Merge remote-tracking branch 'origin/kitkat-dev'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f830c66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+db_files
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..3bd20a7
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,62 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+#
+# Build app code.
+#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-renderscript-files-under, src) \
+    $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+
+LOCAL_SDK_VERSION := 19
+
+LOCAL_PACKAGE_NAME := Launcher3
+#LOCAL_CERTIFICATE := shared
+
+LOCAL_OVERRIDES_PACKAGES := Launcher2
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+#
+# Protocol Buffer Debug Utility in Java
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, util) \
+    $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := protoutil
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..901b638
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3">
+
+    <permission
+        android:name="com.android.launcher3.permission.PRELOAD_WORKSPACE"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="system|signature" />
+    <permission
+        android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_install_shortcut"
+        android:description="@string/permdesc_install_shortcut" />
+    <permission
+        android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_uninstall_shortcut"
+        android:description="@string/permdesc_uninstall_shortcut"/>
+    <permission
+        android:name="com.android.launcher3.permission.READ_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_read_settings"
+        android:description="@string/permdesc_read_settings"/>
+    <permission
+        android:name="com.android.launcher3.permission.WRITE_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="normal"
+        android:label="@string/permlab_write_settings"
+        android:description="@string/permdesc_write_settings"/>
+
+    <permission
+        android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS"
+        android:protectionLevel="signature"
+        />
+
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
+    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
+    <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS" />
+
+    <application
+        android:name="com.android.launcher3.LauncherApplication"
+        android:label="@string/application_name"
+        android:icon="@mipmap/ic_launcher_home"
+        android:hardwareAccelerated="true"
+        android:largeHeap="@bool/config_largeHeap"
+        android:supportsRtl="true">
+        <activity
+            android:name="com.android.launcher3.Launcher"
+            android:launchMode="singleTask"
+            android:clearTaskOnLaunch="true"
+            android:stateNotNeeded="true"
+            android:theme="@style/Theme"
+            android:windowSoftInputMode="adjustPan"
+            android:screenOrientation="nosensor">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.MONKEY"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.android.launcher3.ToggleWeightWatcher"
+            android:label="@string/toggle_weight_watcher"
+            android:enabled="@bool/debug_memory_enabled"
+            android:icon="@mipmap/ic_launcher_home">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.android.launcher3.WallpaperPickerActivity"
+            android:theme="@style/Theme.WallpaperCropper"
+            android:label="@string/pick_wallpaper"
+            android:icon="@mipmap/ic_launcher_wallpaper"
+            android:finishOnCloseSystemDialogs="true"
+            android:process=":wallpaper_chooser">
+            <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.launcher3.WallpaperCropActivity"
+            android:theme="@style/Theme.WallpaperCropper"
+            android:label="@string/crop_wallpaper"
+            android:icon="@mipmap/ic_launcher_wallpaper"
+            android:finishOnCloseSystemDialogs="true"
+            android:process=":wallpaper_chooser">
+            <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>
+
+        <!-- Debugging tools -->
+        <activity
+            android:name="com.android.launcher3.MemoryDumpActivity"
+            android:theme="@android:style/Theme.NoDisplay"
+            android:label="@string/debug_memory_activity"
+            android:enabled="@bool/debug_memory_enabled"
+            android:excludeFromRecents="true"
+            android:icon="@mipmap/ic_launcher_home"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:name="com.android.launcher3.MemoryTracker"
+            android:enabled="@bool/debug_memory_enabled"
+            >
+        </service>
+
+        <!-- Intent received used to prepopulate the default workspace. -->
+        <receiver
+            android:name="com.android.launcher3.PreloadReceiver"
+            android:permission="com.android.launcher3.permission.PRELOAD_WORKSPACE">
+            <intent-filter>
+                <action android:name="com.android.launcher3.action.PRELOAD_WORKSPACE" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Intent received used to install shortcuts from other applications -->
+        <receiver
+            android:name="com.android.launcher3.InstallShortcutReceiver"
+            android:permission="com.android.launcher3.permission.INSTALL_SHORTCUT">
+            <intent-filter>
+                <action android:name="com.android.launcher3.action.INSTALL_SHORTCUT" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Intent received used to uninstall shortcuts from other applications -->
+        <receiver
+            android:name="com.android.launcher3.UninstallShortcutReceiver"
+            android:permission="com.android.launcher3.permission.UNINSTALL_SHORTCUT">
+            <intent-filter>
+                <action android:name="com.android.launcher3.action.UNINSTALL_SHORTCUT" />
+            </intent-filter>
+        </receiver>
+
+        <!-- New user initialization; set up initial wallpaper -->
+        <receiver
+            android:name="com.android.launcher3.UserInitializeReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.USER_INITIALIZE" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="com.android.launcher3.PackageChangedReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_CHANGED"/>
+                <action android:name="android.intent.action.PACKAGE_REPLACED"/>
+                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
+                <data android:scheme="package"></data>
+            </intent-filter>
+        </receiver>
+
+        <!-- The settings provider contains Home's data, like the workspace favorites -->
+        <provider
+            android:name="com.android.launcher3.LauncherProvider"
+            android:authorities="com.android.launcher3.settings"
+            android:exported="true"
+            android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
+            android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />
+
+        <meta-data android:name="android.nfc.disable_beam_default"
+                       android:value="true" />
+    </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..c132395
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,56 @@
+# Copyright (C) 2007 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/fill_screens.py b/fill_screens.py
new file mode 100755
index 0000000..a887792
--- /dev/null
+++ b/fill_screens.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python2.5
+
+import cgi
+import os
+import shutil
+import sys
+import sqlite3
+
+SCREENS = 5
+COLUMNS = 4
+ROWS = 4
+CELL_SIZE = 110
+
+DIR = "db_files"
+AUTO_FILE = "launcher.db"
+
+APPLICATION_COMPONENTS = [
+  "com.android.calculator2/com.android.calculator2.Calculator",
+  "com.android.providers.downloads.ui/com.android.providers.downloads.ui.DownloadList",
+  "com.android.settings/com.android.settings.Settings",
+  "com.android.mms/com.android.mms.ui.ConversationList",
+  "com.android.contacts/com.android.contacts.activities.PeopleActivity",
+  "com.android.dialer/com.android.dialer.DialtactsActivity"
+]
+
+def usage():
+  print "usage: fill_screens.py -- fills up the launcher db"
+
+
+def make_dir():
+  shutil.rmtree(DIR, True)
+  os.makedirs(DIR)
+
+def pull_file(fn):
+  print "pull_file: " + fn
+  rv = os.system("adb pull"
+    + " /data/data/com.android.launcher/databases/launcher.db"
+    + " " + fn);
+  if rv != 0:
+    print "adb pull failed"
+    sys.exit(1)
+
+def push_file(fn):
+  print "push_file: " + fn
+  rv = os.system("adb push"
+    + " " + fn
+    + " /data/data/com.android.launcher/databases/launcher.db")
+  if rv != 0:
+    print "adb push failed"
+    sys.exit(1)
+
+def process_file(fn):
+  print "process_file: " + fn
+  conn = sqlite3.connect(fn)
+  c = conn.cursor()
+  c.execute("DELETE FROM favorites")
+
+  intentFormat = "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=%s;end"
+
+  id = 0;
+  for s in range(SCREENS):
+    for x in range(ROWS):
+      for y in range(COLUMNS):
+        id += 1
+        insert = "INSERT into favorites (_id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconType) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, %d, %d, %d, %d)"
+        insert = insert % (id, "title", "", -100, s, x, y, 1, 1, 2, -1, 0)
+        c.execute(insert)
+        folder_id = id
+
+        for z in range(15):
+          id += 1
+          intent = intentFormat % (APPLICATION_COMPONENTS[id % len(APPLICATION_COMPONENTS)])
+          insert = "INSERT into favorites (_id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconType) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, %d, %d, %d, %d)"
+          insert = insert % (id, "title", intent, folder_id, 0, 0, 0, 1, 1, 0, -1, 0)
+          c.execute(insert)
+
+  conn.commit()
+  c.close()
+
+def main(argv):
+  if len(argv) == 1:
+    make_dir()
+    pull_file(AUTO_FILE)
+    process_file(AUTO_FILE)
+    push_file(AUTO_FILE)
+  else:
+    usage()
+
+if __name__=="__main__":
+  main(sys.argv)
diff --git a/print_db.py b/print_db.py
new file mode 100755
index 0000000..05237d0
--- /dev/null
+++ b/print_db.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python2.5
+
+import cgi
+import codecs
+import os
+import pprint
+import shutil
+import sys
+import sqlite3
+
+SCREENS = 0
+COLUMNS = 4
+ROWS = 4
+HOTSEAT_SIZE = 4
+CELL_SIZE = 110
+
+CONTAINER_DESKTOP = -100
+CONTAINER_HOTSEAT = -101
+
+DIR = "db_files"
+AUTO_FILE = DIR + "/launcher.db"
+INDEX_FILE = DIR + "/index.html"
+
+def usage():
+  print "usage: print_db.py launcher.db <sw600|sw720> -- prints a launcher.db"
+  print "usage: print_db.py <sw600|sw720> -- adb pulls a launcher.db from a device"
+  print "       and prints it"
+  print
+  print "The dump will be created in a directory called db_files in cwd."
+  print "This script will delete any db_files directory you have now"
+
+
+def make_dir():
+  shutil.rmtree(DIR, True)
+  os.makedirs(DIR)
+
+def adb_root_remount():
+  os.system("adb root")
+  os.system("adb remount")
+
+def pull_file(fn):
+  print "pull_file: " + fn
+  rv = os.system("adb pull"
+    + " /data/data/com.google.android.googlequicksearchbox/databases/launcher.db"
+    + " " + fn);
+  if rv != 0:
+    print "adb pull failed"
+    sys.exit(1)
+
+def get_favorites(conn):
+  c = conn.cursor()
+  c.execute("SELECT * FROM favorites")
+  columns = [d[0] for d in c.description]
+  rows = []
+  for row in c:
+    rows.append(row)
+  return columns,rows
+
+def get_screens(conn):
+  c = conn.cursor()
+  c.execute("SELECT * FROM workspaceScreens")
+  columns = [d[0] for d in c.description]
+  rows = []
+  for row in c:
+    rows.append(row)
+  return columns,rows
+
+def print_intent(out, id, i, cell):
+  if cell:
+    out.write("""<span class="intent" title="%s">shortcut</span>""" % (
+        cgi.escape(cell, True)
+      ))
+
+
+def print_icon(out, id, i, cell):
+  if cell:
+    icon_fn = "icon_%d.png" % id
+    out.write("""<img style="width: 3em; height: 3em;" src="%s">""" % ( icon_fn ))
+    f = file(DIR + "/" + icon_fn, "w")
+    f.write(cell)
+    f.close()
+
+def print_icon_type(out, id, i, cell):
+  if cell == 0:
+    out.write("Application (%d)" % cell)
+  elif cell == 1:
+    out.write("Shortcut (%d)" % cell)
+  elif cell == 2:
+    out.write("Folder (%d)" % cell)
+  elif cell == 4:
+    out.write("Widget (%d)" % cell)
+  elif cell:
+    out.write("%d" % cell)
+
+def print_cell(out, id, i, cell):
+  if not cell is None:
+    out.write(cgi.escape(unicode(cell)))
+
+FUNCTIONS = {
+  "intent": print_intent,
+  "icon": print_icon,
+  "iconType": print_icon_type
+}
+
+def render_cell_info(out, cell, occupied):
+  if cell is None:
+    out.write("    <td width=%d height=%d></td>\n" %
+        (CELL_SIZE, CELL_SIZE))
+  elif cell == occupied:
+    pass
+  else:
+    cellX = cell["cellX"]
+    cellY = cell["cellY"]
+    spanX = cell["spanX"]
+    spanY = cell["spanY"]
+    intent = cell["intent"]
+    if intent:
+      title = "title=\"%s\"" % cgi.escape(cell["intent"], True)
+    else:
+      title = ""
+    out.write(("    <td colspan=%d rowspan=%d width=%d height=%d"
+        + " bgcolor=#dddddd align=center valign=middle %s>") % (
+          spanX, spanY,
+          (CELL_SIZE*spanX), (CELL_SIZE*spanY),
+          title))
+    itemType = cell["itemType"]
+    if itemType == 0:
+      out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
+      out.write("<br/>\n")
+      out.write(cgi.escape(cell["title"]) + " <br/><i>(app)</i>")
+    elif itemType == 1:
+      out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
+      out.write("<br/>\n")
+      out.write(cgi.escape(cell["title"]) + " <br/><i>(shortcut)</i>")
+    elif itemType == 2:
+      out.write("""<i>folder</i>""")
+    elif itemType == 4:
+      out.write("<i>widget %d</i><br/>\n" % cell["appWidgetId"])
+    else:
+      out.write("<b>unknown type: %d</b>" % itemType)
+    out.write("</td>\n")
+
+def render_screen_info(out, screen):
+  out.write("<tr>")
+  out.write("<td>%s</td>" % (screen["_id"]))
+  out.write("<td>%s</td>" % (screen["screenRank"]))
+  out.write("</tr>")
+
+def process_file(fn):
+  global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
+  print "process_file: " + fn
+  conn = sqlite3.connect(fn)
+  columns,rows = get_favorites(conn)
+  screenCols, screenRows = get_screens(conn)
+
+  data = [dict(zip(columns,row)) for row in rows]
+  screenData = [dict(zip(screenCols, screenRow)) for screenRow in screenRows]
+
+  # Calculate the proper number of screens, columns, and rows in this db
+  screensIdMap = []
+  hotseatIdMap = []
+  HOTSEAT_SIZE = 0
+  for d in data:
+    if d["spanX"] is None:
+      d["spanX"] = 1
+    if d["spanY"] is None:
+      d["spanY"] = 1
+    if d["container"] == CONTAINER_DESKTOP:
+      if d["screen"] not in screensIdMap:
+        screensIdMap.append(d["screen"])
+      COLUMNS = max(COLUMNS, d["cellX"] + d["spanX"])
+      ROWS = max(ROWS, d["cellX"] + d["spanX"])
+    elif d["container"] == CONTAINER_HOTSEAT:
+      hotseatIdMap.append(d["screen"])
+      HOTSEAT_SIZE = max(HOTSEAT_SIZE, d["screen"] + 1)
+  SCREENS = len(screensIdMap)
+
+  out = codecs.open(INDEX_FILE, encoding="utf-8", mode="w")
+  out.write("""<html>
+<head>
+<style type="text/css">
+.intent {
+  font-style: italic;
+}
+</style>
+</head>
+<body>
+""")
+
+  # Data table
+  out.write("<b>Favorites table</b><br/>\n")
+  out.write("""<html>
+<table border=1 cellspacing=0 cellpadding=4>
+<tr>
+""")
+  print_functions = []
+  for col in columns:
+    print_functions.append(FUNCTIONS.get(col, print_cell))
+  for i in range(0,len(columns)):
+    col = columns[i]
+    out.write("""  <th>%s</th>
+""" % ( col ))
+  out.write("""
+</tr>
+""")
+
+  for row in rows:
+    out.write("""<tr>
+""")
+    for i in range(0,len(row)):
+      cell = row[i]
+      # row[0] is always _id
+      out.write("""  <td>""")
+      print_functions[i](out, row[0], row, cell)
+      out.write("""</td>
+""")
+    out.write("""</tr>
+""")
+  out.write("""</table>
+""")
+
+  # Screens
+  out.write("<br/><b>Screens</b><br/>\n")
+  out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+  out.write("<tr><td>Screen ID</td><td>Rank</td></tr>\n")
+  for screen in screenData:
+    render_screen_info(out, screen)
+  out.write("</table>\n")
+
+  # Hotseat
+  hotseat = []
+  for i in range(0, HOTSEAT_SIZE):
+    hotseat.append(None)
+  for row in data:
+    if row["container"] != CONTAINER_HOTSEAT:
+      continue
+    screen = row["screen"]
+    hotseat[screen] = row
+  out.write("<br/><b>Hotseat</b><br/>\n")
+  out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+  for cell in hotseat:
+    render_cell_info(out, cell, None)
+  out.write("</table>\n")
+
+  # Pages
+  screens = []
+  for i in range(0,SCREENS):
+    screen = []
+    for j in range(0,ROWS):
+      m = []
+      for k in range(0,COLUMNS):
+        m.append(None)
+      screen.append(m)
+    screens.append(screen)
+  occupied = "occupied"
+  for row in data:
+    # desktop
+    if row["container"] != CONTAINER_DESKTOP:
+      continue
+    screen = screens[screensIdMap.index(row["screen"])]
+    cellX = row["cellX"]
+    cellY = row["cellY"]
+    spanX = row["spanX"]
+    spanY = row["spanY"]
+    for j in range(cellY, cellY+spanY):
+      for k in range(cellX, cellX+spanX):
+        screen[j][k] = occupied
+    screen[cellY][cellX] = row
+  i=0
+  for screen in screens:
+    out.write("<br/><b>Screen %d</b><br/>\n" % i)
+    out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+    for m in screen:
+      out.write("  <tr>\n")
+      for cell in m:
+        render_cell_info(out, cell, occupied)
+      out.write("</tr>\n")
+    out.write("</table>\n")
+    i=i+1
+
+  out.write("""
+</body>
+</html>
+""")
+
+  out.close()
+
+def updateDeviceClassConstants(str):
+  global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
+  devClass = str.lower()
+  if devClass == "sw600":
+    COLUMNS = 6
+    ROWS = 6
+    HOTSEAT_SIZE = 6
+    return True
+  elif devClass == "sw720":
+    COLUMNS = 8
+    ROWS = 6
+    HOTSEAT_SIZE = 8
+    return True
+  return False
+
+def main(argv):
+  if len(argv) == 1 or (len(argv) == 2 and updateDeviceClassConstants(argv[1])):
+    make_dir()
+    adb_root_remount()
+    pull_file(AUTO_FILE)
+    process_file(AUTO_FILE)
+  elif len(argv) == 2 or (len(argv) == 3 and updateDeviceClassConstants(argv[2])):
+    make_dir()
+    process_file(argv[1])
+  else:
+    usage()
+
+if __name__=="__main__":
+  main(sys.argv)
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..9b59b21
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,51 @@
+-keep class com.android.launcher3.Launcher {
+  public void previousScreen(android.view.View);
+  public void nextScreen(android.view.View);
+  public void launchHotSeat(android.view.View);
+  public void onClickSearchButton(android.view.View);
+  public void onClickVoiceButton(android.view.View);
+  public void onClickConfigureButton(android.view.View);
+  public void onClickAllAppsButton(android.view.View);
+  public void onClickAppMarketButton(android.view.View);
+  public void dismissFirstRunCling(android.view.View);
+  public void dismissWorkspaceCling(android.view.View);
+  public void dismissAllAppsCling(android.view.View);
+}
+
+-keep class com.android.launcher3.CellLayout {
+  public float getBackgroundAlpha();
+  public void setBackgroundAlpha(float);
+}
+
+-keep class com.android.launcher3.DragLayer$LayoutParams {
+  public void setWidth(int);
+  public int getWidth();
+  public void setHeight(int);
+  public int getHeight();
+  public void setX(int);
+  public int getX();
+  public void setY(int);
+  public int getY();
+}
+
+-keep class com.android.launcher3.CellLayout$LayoutParams {
+  public void setWidth(int);
+  public int getWidth();
+  public void setHeight(int);
+  public int getHeight();
+  public void setX(int);
+  public int getX();
+  public void setY(int);
+  public int getY();
+}
+
+-keep class com.android.launcher3.Workspace {
+  public float getBackgroundAlpha();
+  public void setBackgroundAlpha(float);
+  public float getChildrenOutlineAlpha();
+  public void setChildrenOutlineAlpha(float);
+}
+
+-keep class com.android.launcher3.MemoryDumpActivity {
+  *;
+}
diff --git a/protos/backup.proto b/protos/backup.proto
new file mode 100644
index 0000000..7ba2937
--- /dev/null
+++ b/protos/backup.proto
@@ -0,0 +1,85 @@
+/*
+ * 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 launcher_backup;
+
+option java_package = "com.android.launcher3.backup";
+option java_outer_classname = "BackupProtos";
+
+message Key {
+  enum Type {
+    FAVORITE = 1;
+    SCREEN = 2;
+    ICON = 3;
+    WIDGET = 4;
+  }
+  required Type type = 1;
+  optional string name = 2;  // keep this short
+  optional int64 id = 3;
+  optional int64 checksum = 4;
+}
+
+message CheckedMessage {
+  required bytes payload = 1;
+  required int64 checksum = 2;
+}
+
+message Journal {
+  required int32 app_version = 1;
+  required int64 t = 2;
+  optional int64 bytes = 3;
+  optional int32 rows = 4;
+  repeated Key key = 5;
+}
+
+message Favorite {
+  required int64 id = 1;
+  required int32 itemType = 2;
+  optional string title = 3;
+  optional int32 container = 4;
+  optional int32 screen = 5;
+  optional int32 cellX = 6;
+  optional int32 cellY = 7;
+  optional int32 spanX = 8;
+  optional int32 spanY = 9;
+  optional int32 displayMode = 10;
+  optional int32 appWidgetId = 11;
+  optional string appWidgetProvider = 12;
+  optional string intent = 13;
+  optional string uri = 14;
+  optional int32 iconType = 15;
+  optional string iconPackage = 16;
+  optional string iconResource = 17;
+  optional bytes icon = 18;
+ }
+
+message Screen {
+  required int64 id = 1;
+  optional int32 rank = 2;
+ }
+
+message Resource {
+  required int32 dpi = 1;
+  required bytes data = 2;
+ }
+
+message Widget {
+  required string provider = 1;
+  optional string label = 2;
+  optional bool configure = 3;
+  optional Resource icon = 4;
+  optional Resource preview = 5;
+ }
diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml
new file mode 100644
index 0000000..4fa9847
--- /dev/null
+++ b/res/anim/fade_in_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/accelerate_interpolator"
+
+    android:fromAlpha="0.0"
+    android:toAlpha="1.0"
+
+    android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml
new file mode 100644
index 0000000..a061a6c
--- /dev/null
+++ b/res/anim/fade_out_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/accelerate_interpolator"
+
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+
+    android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/drawable-hdpi/apps_customize_bg.png b/res/drawable-hdpi/apps_customize_bg.png
new file mode 100644
index 0000000..2847467
--- /dev/null
+++ b/res/drawable-hdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_appwidget_error.9.png b/res/drawable-hdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..4da3195
--- /dev/null
+++ b/res/drawable-hdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling1.png b/res/drawable-hdpi/bg_cling1.png
new file mode 100644
index 0000000..0e15532
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling2.png b/res/drawable-hdpi/bg_cling2.png
new file mode 100644
index 0000000..e65d9a2
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling3.png b/res/drawable-hdpi/bg_cling3.png
new file mode 100644
index 0000000..ea71fbd
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling4.png b/res/drawable-hdpi/bg_cling4.png
new file mode 100644
index 0000000..9403667
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-hdpi/cling.9.png b/res/drawable-hdpi/cling.9.png
new file mode 100644
index 0000000..36fbfc8
--- /dev/null
+++ b/res/drawable-hdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_down.png b/res/drawable-hdpi/cling_arrow_down.png
new file mode 100644
index 0000000..4f521ea
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_left.png b/res/drawable-hdpi/cling_arrow_left.png
new file mode 100644
index 0000000..13764c9
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_right.png b/res/drawable-hdpi/cling_arrow_right.png
new file mode 100644
index 0000000..be52244
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_up.png b/res/drawable-hdpi/cling_arrow_up.png
new file mode 100644
index 0000000..83b5b37
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_button.9.png b/res/drawable-hdpi/cling_button.9.png
new file mode 100644
index 0000000..e308382
--- /dev/null
+++ b/res/drawable-hdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_button_pressed.9.png b/res/drawable-hdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..4f9ca6f
--- /dev/null
+++ b/res/drawable-hdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/custom_content_page.png b/res/drawable-hdpi/custom_content_page.png
new file mode 100644
index 0000000..9eef50c
--- /dev/null
+++ b/res/drawable-hdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-hdpi/focused_bg.9.png b/res/drawable-hdpi/focused_bg.9.png
new file mode 100644
index 0000000..2925ae8
--- /dev/null
+++ b/res/drawable-hdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hand.png b/res/drawable-hdpi/hand.png
new file mode 100644
index 0000000..bd4f6df
--- /dev/null
+++ b/res/drawable-hdpi/hand.png
Binary files differ
diff --git a/res/drawable-hdpi/home_press.9.png b/res/drawable-hdpi/home_press.9.png
new file mode 100644
index 0000000..ef07011
--- /dev/null
+++ b/res/drawable-hdpi/home_press.9.png
Binary files differ
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_allapps.png b/res/drawable-hdpi/ic_allapps.png
new file mode 100644
index 0000000..e7677d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..863eeba
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_all_apps_holo_dark.png b/res/drawable-hdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..1dc02d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_search_normal_holo.png b/res/drawable-hdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..3f64d68
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_voice_search_holo.png b/res/drawable-hdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..dae5446
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_voice_search_holo.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..0003c6c
--- /dev/null
+++ b/res/drawable-hdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_clear_active_holo.png b/res/drawable-hdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..cdd0052
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_clear_normal_holo.png b/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..84549ff
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_info_active_holo.png b/res/drawable-hdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..c534e56
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_info_normal_holo.png b/res/drawable-hdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..c9bcd7f
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_market_holo.png b/res/drawable-hdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..dc78251
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..82b1b59
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..3fc2e83
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_add.png b/res/drawable-hdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..c37d622
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..aac8d40
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..bafd94b
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting.png b/res/drawable-hdpi/ic_setting.png
new file mode 100644
index 0000000..c617154
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..fb58a4b
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper.png b/res/drawable-hdpi/ic_wallpaper.png
new file mode 100644
index 0000000..5e5d118
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..d104e57
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget.png b/res/drawable-hdpi/ic_widget.png
new file mode 100644
index 0000000..8c57af0
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..081f9f9
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/overscroll_glow_left.9.png b/res/drawable-hdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..aaf43c7
--- /dev/null
+++ b/res/drawable-hdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-hdpi/overscroll_glow_right.9.png b/res/drawable-hdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..d034864
--- /dev/null
+++ b/res/drawable-hdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_holo.9.png b/res/drawable-hdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..8a1aa5f
--- /dev/null
+++ b/res/drawable-hdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_holo.9.png b/res/drawable-hdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..abf8f51
--- /dev/null
+++ b/res/drawable-hdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/paged_view_indicator.9.png b/res/drawable-hdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..5b47f44
--- /dev/null
+++ b/res/drawable-hdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_container_holo.9.png b/res/drawable-hdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..ba8a4a8
--- /dev/null
+++ b/res/drawable-hdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_holo.png b/res/drawable-hdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..857a01e
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_nolip_holo.png b/res/drawable-hdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..53df36a
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_outer_holo.png b/res/drawable-hdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..b711cf3
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_rest.png b/res/drawable-hdpi/portal_ring_rest.png
new file mode 100644
index 0000000..2979b73
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
new file mode 100644
index 0000000..36e7dfd
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..3321fc9
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-hdpi/search_frame.9.png b/res/drawable-hdpi/search_frame.9.png
new file mode 100644
index 0000000..15ca1f4
--- /dev/null
+++ b/res/drawable-hdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_focused_holo.9.png b/res/drawable-hdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..673e3bf
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_holo.9.png b/res/drawable-hdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..d57df98
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..4b312d9
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..6278eef
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..294991d
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_holo.9.png b/res/drawable-hdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..19532ab
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..5140b35
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..aadc6f8
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_focused.9.png b/res/drawable-hdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..c72d6a2
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_pressed.9.png b/res/drawable-hdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..44c65ac
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_selected.9.png b/res/drawable-hdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..461bacb
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_selected.9.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-hdpi/widget_container_holo.9.png b/res/drawable-hdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..8c15a7c
--- /dev/null
+++ b/res/drawable-hdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_frame_holo.9.png b/res/drawable-hdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..2d6fcf5
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_bottom.png b/res/drawable-hdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..f0afd61
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_left.png b/res/drawable-hdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..47613b2
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_right.png b/res/drawable-hdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..acc28be
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_top.png b/res/drawable-hdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..2c60be0
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
new file mode 100644
index 0000000..310ff8b
--- /dev/null
+++ b/res/drawable-hdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-hdpi/workspace_bg.9.png b/res/drawable-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/res/drawable-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling1.png b/res/drawable-land-hdpi/bg_cling1.png
new file mode 100644
index 0000000..7123c5c
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling2.png b/res/drawable-land-hdpi/bg_cling2.png
new file mode 100644
index 0000000..889b627
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling3.png b/res/drawable-land-hdpi/bg_cling3.png
new file mode 100644
index 0000000..4ff338c
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-hdpi/ic_home_voice_search_holo.png b/res/drawable-land-hdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..5a7fc99
--- /dev/null
+++ b/res/drawable-land-hdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-hdpi/workspace_bg.9.png b/res/drawable-land-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..1a58144
--- /dev/null
+++ b/res/drawable-land-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling1.png b/res/drawable-land-mdpi/bg_cling1.png
new file mode 100644
index 0000000..f5faeb4
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling2.png b/res/drawable-land-mdpi/bg_cling2.png
new file mode 100644
index 0000000..963967d
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling3.png b/res/drawable-land-mdpi/bg_cling3.png
new file mode 100644
index 0000000..921831a
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-mdpi/ic_home_voice_search_holo.png b/res/drawable-land-mdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..ee7dde5
--- /dev/null
+++ b/res/drawable-land-mdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-mdpi/workspace_bg.9.png b/res/drawable-land-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..a12519e
--- /dev/null
+++ b/res/drawable-land-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling1.png b/res/drawable-land-xhdpi/bg_cling1.png
new file mode 100644
index 0000000..2282117
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling2.png b/res/drawable-land-xhdpi/bg_cling2.png
new file mode 100644
index 0000000..5243889
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling3.png b/res/drawable-land-xhdpi/bg_cling3.png
new file mode 100644
index 0000000..08475f7
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/ic_home_voice_search_holo.png b/res/drawable-land-xhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..56bbbbb
--- /dev/null
+++ b/res/drawable-land-xhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/workspace_bg.9.png b/res/drawable-land-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..ce41454
--- /dev/null
+++ b/res/drawable-land-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling1.png b/res/drawable-land-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000..78943f0
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling2.png b/res/drawable-land-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000..98b6568
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling3.png b/res/drawable-land-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000..e249fe5
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png b/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..6ea7368
--- /dev/null
+++ b/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/workspace_bg.9.png b/res/drawable-land-xxhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..b0b4561
--- /dev/null
+++ b/res/drawable-land-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/apps_customize_bg.png b/res/drawable-mdpi/apps_customize_bg.png
new file mode 100644
index 0000000..2847467
--- /dev/null
+++ b/res/drawable-mdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_appwidget_error.9.png b/res/drawable-mdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..493c0d4
--- /dev/null
+++ b/res/drawable-mdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling1.png b/res/drawable-mdpi/bg_cling1.png
new file mode 100644
index 0000000..f284412
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling2.png b/res/drawable-mdpi/bg_cling2.png
new file mode 100644
index 0000000..0052dc2
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling3.png b/res/drawable-mdpi/bg_cling3.png
new file mode 100644
index 0000000..fabdf7a
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling4.png b/res/drawable-mdpi/bg_cling4.png
new file mode 100644
index 0000000..2f152f4d
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling5.png b/res/drawable-mdpi/bg_cling5.png
new file mode 100644
index 0000000..e094809
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling5.png
Binary files differ
diff --git a/res/drawable-mdpi/cling.9.png b/res/drawable-mdpi/cling.9.png
new file mode 100644
index 0000000..4c0f139
--- /dev/null
+++ b/res/drawable-mdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_down.png b/res/drawable-mdpi/cling_arrow_down.png
new file mode 100644
index 0000000..58e66fb
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_left.png b/res/drawable-mdpi/cling_arrow_left.png
new file mode 100644
index 0000000..023c717
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_right.png b/res/drawable-mdpi/cling_arrow_right.png
new file mode 100644
index 0000000..cf0eb10
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_up.png b/res/drawable-mdpi/cling_arrow_up.png
new file mode 100644
index 0000000..9b0e6b7
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_button.9.png b/res/drawable-mdpi/cling_button.9.png
new file mode 100644
index 0000000..a0b6f97
--- /dev/null
+++ b/res/drawable-mdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_button_pressed.9.png b/res/drawable-mdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..986e669
--- /dev/null
+++ b/res/drawable-mdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/custom_content_page.png b/res/drawable-mdpi/custom_content_page.png
new file mode 100644
index 0000000..cc4005d
--- /dev/null
+++ b/res/drawable-mdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-mdpi/focused_bg.9.png b/res/drawable-mdpi/focused_bg.9.png
new file mode 100644
index 0000000..89c29ac
--- /dev/null
+++ b/res/drawable-mdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/hand.png b/res/drawable-mdpi/hand.png
new file mode 100644
index 0000000..fe5a035
--- /dev/null
+++ b/res/drawable-mdpi/hand.png
Binary files differ
diff --git a/res/drawable-mdpi/home_press.9.png b/res/drawable-mdpi/home_press.9.png
new file mode 100644
index 0000000..679a1f6
--- /dev/null
+++ b/res/drawable-mdpi/home_press.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_allapps.png b/res/drawable-mdpi/ic_allapps.png
new file mode 100644
index 0000000..e0fd9c0
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..3bd87b1
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_all_apps_holo_dark.png b/res/drawable-mdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..84fa594
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_search_normal_holo.png b/res/drawable-mdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..7367c38
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_voice_search_holo.png b/res/drawable-mdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..f211a7b
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_voice_search_holo.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..aabc123
--- /dev/null
+++ b/res/drawable-mdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_clear_active_holo.png b/res/drawable-mdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..2683bea
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_clear_normal_holo.png b/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..219f3e5
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_info_active_holo.png b/res/drawable-mdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..f84b4a6
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_info_normal_holo.png b/res/drawable-mdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..eac578f
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_market_holo.png b/res/drawable-mdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..cacb374
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..0350e55
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..799b62f
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_add.png b/res/drawable-mdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..8e05e64
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..ab5f4c8
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..c919ee8
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting.png b/res/drawable-mdpi/ic_setting.png
new file mode 100644
index 0000000..0c8ae9d
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_icn.png b/res/drawable-mdpi/ic_setting_icn.png
new file mode 100644
index 0000000..5c32c54
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_icn.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..846091f
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper.png b/res/drawable-mdpi/ic_wallpaper.png
new file mode 100644
index 0000000..333a206
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..273c48b
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget.png b/res/drawable-mdpi/ic_widget.png
new file mode 100644
index 0000000..5f974c2
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..0a3e883
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_left.9.png b/res/drawable-mdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..b79cdcd
--- /dev/null
+++ b/res/drawable-mdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_right.9.png b/res/drawable-mdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..1321303
--- /dev/null
+++ b/res/drawable-mdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_holo.9.png b/res/drawable-mdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..561d3cd
--- /dev/null
+++ b/res/drawable-mdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_holo.9.png b/res/drawable-mdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..2681f23
--- /dev/null
+++ b/res/drawable-mdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/paged_view_indicator.9.png b/res/drawable-mdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..647b60f
--- /dev/null
+++ b/res/drawable-mdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_container_holo.9.png b/res/drawable-mdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..1e4afae
--- /dev/null
+++ b/res/drawable-mdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_holo.png b/res/drawable-mdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..72e0af8
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_nolip_holo.png b/res/drawable-mdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..483f0eb
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_outer_holo.png b/res/drawable-mdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..e9b35f3
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_rest.png b/res/drawable-mdpi/portal_ring_rest.png
new file mode 100644
index 0000000..d0a976e
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
new file mode 100644
index 0000000..4de3017
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..dd77406
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-mdpi/search_frame.9.png b/res/drawable-mdpi/search_frame.9.png
new file mode 100644
index 0000000..058905b
--- /dev/null
+++ b/res/drawable-mdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_focused_holo.9.png b/res/drawable-mdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..c9972e7
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_holo.9.png b/res/drawable-mdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..587337c
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..284f534
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..155c4fc
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..f0cecd1
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_holo.9.png b/res/drawable-mdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..a2dbf42
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..f1a2819
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..b1223fe
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_focused.9.png b/res/drawable-mdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..13b325b
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_pressed.9.png b/res/drawable-mdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..4e8196d
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_selected.9.png b/res/drawable-mdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..eee69ec
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_selected.9.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-mdpi/widget_container_holo.9.png b/res/drawable-mdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..db24457
--- /dev/null
+++ b/res/drawable-mdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_frame_holo.9.png b/res/drawable-mdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..028bd62
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_bottom.png b/res/drawable-mdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..c838bf4
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_left.png b/res/drawable-mdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..ff0b0d3
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_right.png b/res/drawable-mdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..fc4808e
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_top.png b/res/drawable-mdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..3b1df01
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
new file mode 100644
index 0000000..1ba559d
--- /dev/null
+++ b/res/drawable-mdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-mdpi/workspace_bg.9.png b/res/drawable-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/res/drawable-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..98f7ac8
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..0a511f9
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps.png b/res/drawable-sw600dp-hdpi/ic_allapps.png
new file mode 100644
index 0000000..8bda435
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..07ff331
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..3928e2c
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..e34de34
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..b3be472
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..96b981c
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..bc13a26
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..3ae4aff
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..31148dd
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps.png b/res/drawable-sw600dp-mdpi/ic_allapps.png
new file mode 100644
index 0000000..e2afea5
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..d409c7e
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..58ec10c
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..a986fd3
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..319c074
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..8537714
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..365dcfc
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..c25b590
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..24ac880
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps.png b/res/drawable-sw600dp-xhdpi/ic_allapps.png
new file mode 100644
index 0000000..8fed290
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..786b676
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..b66dd2f
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..3ccce33
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..d4ce45f
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..9aa13c9
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..0106cd6
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..c661f68
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..bf6ab97
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps.png b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000..2429656
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..b93a51b
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..472c3f9
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..e30cc97
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..65b2541
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..5068646
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..6628425
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw720dp-hdpi/workspace_bg.9.png b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-mdpi/workspace_bg.9.png b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/apps_customize_bg.png b/res/drawable-xhdpi/apps_customize_bg.png
new file mode 100644
index 0000000..a51cc11
--- /dev/null
+++ b/res/drawable-xhdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_appwidget_error.9.png b/res/drawable-xhdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..b792cc8
--- /dev/null
+++ b/res/drawable-xhdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling1.png b/res/drawable-xhdpi/bg_cling1.png
new file mode 100644
index 0000000..b71351a
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling2.png b/res/drawable-xhdpi/bg_cling2.png
new file mode 100644
index 0000000..ad78dfe
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling3.png b/res/drawable-xhdpi/bg_cling3.png
new file mode 100644
index 0000000..ae04195
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling4.png b/res/drawable-xhdpi/bg_cling4.png
new file mode 100644
index 0000000..f4bb83e
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling.9.png b/res/drawable-xhdpi/cling.9.png
new file mode 100644
index 0000000..1cb4681
--- /dev/null
+++ b/res/drawable-xhdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_down.png b/res/drawable-xhdpi/cling_arrow_down.png
new file mode 100644
index 0000000..ee10933
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_left.png b/res/drawable-xhdpi/cling_arrow_left.png
new file mode 100644
index 0000000..cffbcf3
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_right.png b/res/drawable-xhdpi/cling_arrow_right.png
new file mode 100644
index 0000000..d880d67
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_up.png b/res/drawable-xhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..fd2c60c
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_button.9.png b/res/drawable-xhdpi/cling_button.9.png
new file mode 100644
index 0000000..4192563
--- /dev/null
+++ b/res/drawable-xhdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_button_pressed.9.png b/res/drawable-xhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..d3ce469
--- /dev/null
+++ b/res/drawable-xhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/custom_content_page.png b/res/drawable-xhdpi/custom_content_page.png
new file mode 100644
index 0000000..e1da91c
--- /dev/null
+++ b/res/drawable-xhdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-xhdpi/focused_bg.9.png b/res/drawable-xhdpi/focused_bg.9.png
new file mode 100644
index 0000000..197a269
--- /dev/null
+++ b/res/drawable-xhdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/hand.png b/res/drawable-xhdpi/hand.png
new file mode 100644
index 0000000..35b678c
--- /dev/null
+++ b/res/drawable-xhdpi/hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/home_press.9.png b/res/drawable-xhdpi/home_press.9.png
new file mode 100644
index 0000000..d9abfd3
--- /dev/null
+++ b/res/drawable-xhdpi/home_press.9.png
Binary files differ
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_allapps.png b/res/drawable-xhdpi/ic_allapps.png
new file mode 100644
index 0000000..f71964c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..d678f02
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png b/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..81228d4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_search_normal_holo.png b/res/drawable-xhdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..0fe1cd1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_voice_search_holo.png b/res/drawable-xhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..1fc5cc8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_voice_search_holo.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..3f21faf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_clear_active_holo.png b/res/drawable-xhdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..1a7e53d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png b/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..d4965d9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_info_active_holo.png b/res/drawable-xhdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..b8cdbc4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_info_normal_holo.png b/res/drawable-xhdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..f503fb8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_market_holo.png b/res/drawable-xhdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..958f0de
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..c155274
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..2ec7ad9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_add.png b/res/drawable-xhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..28e164b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..aed3d71
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..0887416
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting.png b/res/drawable-xhdpi/ic_setting.png
new file mode 100644
index 0000000..91ba98c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..08aafc4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper.png b/res/drawable-xhdpi/ic_wallpaper.png
new file mode 100644
index 0000000..41dc000
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..ffff053
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget.png b/res/drawable-xhdpi/ic_widget.png
new file mode 100644
index 0000000..47dcdd1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..8bb387b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_left.9.png b/res/drawable-xhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..4f248f7
--- /dev/null
+++ b/res/drawable-xhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_right.9.png b/res/drawable-xhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..818a70d
--- /dev/null
+++ b/res/drawable-xhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_holo.9.png b/res/drawable-xhdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..4972a2e
--- /dev/null
+++ b/res/drawable-xhdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_holo.9.png b/res/drawable-xhdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..b99461f
--- /dev/null
+++ b/res/drawable-xhdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/paged_view_indicator.9.png b/res/drawable-xhdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..fb8a228
--- /dev/null
+++ b/res/drawable-xhdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_container_holo.9.png b/res/drawable-xhdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..cedbdc4
--- /dev/null
+++ b/res/drawable-xhdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner_holo.png b/res/drawable-xhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..f9acfa2
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..eb2cf5f
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_outer_holo.png b/res/drawable-xhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..f32051d
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_rest.png b/res/drawable-xhdpi/portal_ring_rest.png
new file mode 100644
index 0000000..ff0369a
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
new file mode 100644
index 0000000..b4b828d
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..a44dc11
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/search_frame.9.png b/res/drawable-xhdpi/search_frame.9.png
new file mode 100644
index 0000000..32a0714
--- /dev/null
+++ b/res/drawable-xhdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..03cfb09
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_holo.9.png b/res/drawable-xhdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..e4229f2
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..2412711
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..e862cb1
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..f3a5cbd
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_holo.9.png b/res/drawable-xhdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..9465173
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..1653600
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..f1eb673
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_focused.9.png b/res/drawable-xhdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..279e859
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_pressed.9.png b/res/drawable-xhdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..abe0e00
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_selected.9.png b/res/drawable-xhdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..b047591
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_selected.9.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-xhdpi/widget_container_holo.9.png b/res/drawable-xhdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..1313fe7
--- /dev/null
+++ b/res/drawable-xhdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_frame_holo.9.png b/res/drawable-xhdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..76cec60
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_bottom.png b/res/drawable-xhdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..19437d7
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_left.png b/res/drawable-xhdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..28c5487
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_right.png b/res/drawable-xhdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..4f672a6
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_top.png b/res/drawable-xhdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..e866c00
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
new file mode 100644
index 0000000..9730f35
--- /dev/null
+++ b/res/drawable-xhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xhdpi/workspace_bg.9.png b/res/drawable-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/res/drawable-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/arrow_dashed.png b/res/drawable-xxhdpi/arrow_dashed.png
new file mode 100644
index 0000000..b64f4d0
--- /dev/null
+++ b/res/drawable-xxhdpi/arrow_dashed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling1.png b/res/drawable-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000..0777856
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling2.png b/res/drawable-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000..1797a1b
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling3.png b/res/drawable-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000..a87be63
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling4.png b/res/drawable-xxhdpi/bg_cling4.png
new file mode 100644
index 0000000..cabe919
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling_home.png b/res/drawable-xxhdpi/bg_cling_home.png
new file mode 100644
index 0000000..1ae93e7
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling_home.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling_nakasi3.png b/res/drawable-xxhdpi/bg_cling_nakasi3.png
new file mode 100644
index 0000000..f47236c
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling_nakasi3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling.9.png b/res/drawable-xxhdpi/cling.9.png
new file mode 100644
index 0000000..7beae03
--- /dev/null
+++ b/res/drawable-xxhdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_down.png b/res/drawable-xxhdpi/cling_arrow_down.png
new file mode 100644
index 0000000..48c4f06
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_left.png b/res/drawable-xxhdpi/cling_arrow_left.png
new file mode 100644
index 0000000..8760d05
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_right.png b/res/drawable-xxhdpi/cling_arrow_right.png
new file mode 100644
index 0000000..356ba17
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_up.png b/res/drawable-xxhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..4cb805f
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_button.9.png b/res/drawable-xxhdpi/cling_button.9.png
new file mode 100644
index 0000000..e412876
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_button_pressed.9.png b/res/drawable-xxhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..55e89da
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/default_widget_preview_holo.9.png b/res/drawable-xxhdpi/default_widget_preview_holo.9.png
new file mode 100644
index 0000000..0f62097
--- /dev/null
+++ b/res/drawable-xxhdpi/default_widget_preview_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/focused_bg.9.png b/res/drawable-xxhdpi/focused_bg.9.png
new file mode 100644
index 0000000..84d3062
--- /dev/null
+++ b/res/drawable-xxhdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hand.png b/res/drawable-xxhdpi/hand.png
new file mode 100644
index 0000000..88c2a88
--- /dev/null
+++ b/res/drawable-xxhdpi/hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_bg_panel.9.png b/res/drawable-xxhdpi/hotseat_bg_panel.9.png
new file mode 100644
index 0000000..40fc076
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_bg_panel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png b/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png
new file mode 100644
index 0000000..8a77536
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_track_holo.9.png b/res/drawable-xxhdpi/hotseat_track_holo.9.png
new file mode 100644
index 0000000..dd2216f
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_track_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000..624e0ef
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..77b45ae
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png b/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..0b8e88c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png b/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png
new file mode 100644
index 0000000..a47b2ba
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png b/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png
new file mode 100644
index 0000000..75625d1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_search_normal_holo.png b/res/drawable-xxhdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..a9523d3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_search_pressed_holo.png b/res/drawable-xxhdpi/ic_home_search_pressed_holo.png
new file mode 100644
index 0000000..800d994
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_search_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_voice_search_holo.png b/res/drawable-xxhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..c9c0b50
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png b/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png
new file mode 100644
index 0000000..27a5897
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.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..d00db9a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png b/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..95cf841
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..b0f5a27
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_info_active_holo.png b/res/drawable-xxhdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..57f332a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..94f0955
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..3bb098c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..550cc5b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_add.png b/res/drawable-xxhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..fd8a662
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..08615f3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..9d4fbf8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_qs_remote_display.png b/res/drawable-xxhdpi/ic_qs_remote_display.png
new file mode 100644
index 0000000..25ea9fa
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_qs_remote_display.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
new file mode 100644
index 0000000..33a8d2d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting.png b/res/drawable-xxhdpi/ic_setting.png
new file mode 100644
index 0000000..6e1e662
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..a202a40
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper.png b/res/drawable-xxhdpi/ic_wallpaper.png
new file mode 100644
index 0000000..c718444
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..03324aa
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget.png b/res/drawable-xxhdpi/ic_widget.png
new file mode 100644
index 0000000..fddfeca
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..3d3670e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_left.9.png b/res/drawable-xxhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..1a895cd
--- /dev/null
+++ b/res/drawable-xxhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_right.9.png b/res/drawable-xxhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..5766761
--- /dev/null
+++ b/res/drawable-xxhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_holo.9.png b/res/drawable-xxhdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..626aafb
--- /dev/null
+++ b/res/drawable-xxhdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_holo.9.png b/res/drawable-xxhdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..66257dc
--- /dev/null
+++ b/res/drawable-xxhdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_container_holo.9.png b/res/drawable-xxhdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..599a076
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner_holo.png b/res/drawable-xxhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..8cd6a59
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..0fad656
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_outer_holo.png b/res/drawable-xxhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..0aee4f0
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_rest.png b/res/drawable-xxhdpi/portal_ring_rest.png
new file mode 100644
index 0000000..6fa6a53
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
new file mode 100644
index 0000000..c44f3b8
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..1ab18da
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/search_bg_panel.9.png b/res/drawable-xxhdpi/search_bg_panel.9.png
new file mode 100644
index 0000000..85cae17
--- /dev/null
+++ b/res/drawable-xxhdpi/search_bg_panel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/search_frame.9.png b/res/drawable-xxhdpi/search_frame.9.png
new file mode 100644
index 0000000..f297bf1
--- /dev/null
+++ b/res/drawable-xxhdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..2400c65
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_holo.9.png b/res/drawable-xxhdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..5067cbb
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..84c246d
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..939e0c3
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_holo.9.png b/res/drawable-xxhdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..62ca6cf
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..58ac0d6
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_focused.9.png b/res/drawable-xxhdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..1004c14
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_pressed.9.png b/res/drawable-xxhdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..9658444
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_selected.9.png b/res/drawable-xxhdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..a3cd303
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_selected.9.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-xxhdpi/widget_container_holo.9.png b/res/drawable-xxhdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..8f79920
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_frame_holo.9.png b/res/drawable-xxhdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..1681387
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_bottom.png b/res/drawable-xxhdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..d549fcd
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_left.png b/res/drawable-xxhdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..dd56dad
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_right.png b/res/drawable-xxhdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..296a1c1
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_top.png b/res/drawable-xxhdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..e86270a
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
new file mode 100644
index 0000000..3a3790d
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xxhdpi/workspace_bg.9.png b/res/drawable-xxhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..efc9b04
--- /dev/null
+++ b/res/drawable-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable/all_apps_button_icon.xml b/res/drawable/all_apps_button_icon.xml
new file mode 100644
index 0000000..7c69cad
--- /dev/null
+++ b/res/drawable/all_apps_button_icon.xml
@@ -0,0 +1,21 @@
+<?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_focused="true" android:drawable="@drawable/ic_allapps_pressed" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed" />
+    <item android:drawable="@drawable/ic_allapps" />
+</selector>
diff --git a/res/drawable/cling_arrow_end.xml b/res/drawable/cling_arrow_end.xml
new file mode 100644
index 0000000..3f63c7d
--- /dev/null
+++ b/res/drawable/cling_arrow_end.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/cling_arrow_right"
+        android:autoMirrored="true">
+</bitmap>
diff --git a/res/drawable/cling_arrow_start.xml b/res/drawable/cling_arrow_start.xml
new file mode 100644
index 0000000..ebe9183
--- /dev/null
+++ b/res/drawable/cling_arrow_start.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/cling_arrow_left"
+        android:autoMirrored="true">
+</bitmap>
diff --git a/res/drawable/cling_button_bg.xml b/res/drawable/cling_button_bg.xml
new file mode 100644
index 0000000..7bf6ce7
--- /dev/null
+++ b/res/drawable/cling_button_bg.xml
@@ -0,0 +1,20 @@
+<?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_pressed="true" android:drawable="@drawable/cling_button_pressed" />
+    <item android:drawable="@drawable/cling_button" />
+</selector>
diff --git a/res/drawable/focusable_view_bg.xml b/res/drawable/focusable_view_bg.xml
new file mode 100644
index 0000000..66661e2
--- /dev/null
+++ b/res/drawable/focusable_view_bg.xml
@@ -0,0 +1,19 @@
+<?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_focused="true" android:drawable="@drawable/focused_bg" />
+</selector>
diff --git a/res/drawable/info_target_selector.xml b/res/drawable/info_target_selector.xml
new file mode 100644
index 0000000..f3a7016
--- /dev/null
+++ b/res/drawable/info_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_launcher_info_normal_holo"  />
+    <item android:drawable="@drawable/ic_launcher_info_active_holo"  />
+</transition>
diff --git a/res/drawable/remove_target_selector.xml b/res/drawable/remove_target_selector.xml
new file mode 100644
index 0000000..5e071fb
--- /dev/null
+++ b/res/drawable/remove_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_launcher_clear_normal_holo"  />
+    <item android:drawable="@drawable/ic_launcher_clear_active_holo"  />
+</transition>
diff --git a/res/drawable/setting_button.xml b/res/drawable/setting_button.xml
new file mode 100644
index 0000000..4d66a1a
--- /dev/null
+++ b/res/drawable/setting_button.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_setting_pressed" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_setting_pressed" />
+    <item android:drawable="@drawable/ic_setting" />
+</selector>
diff --git a/res/drawable/tab_widget_indicator_selector.xml b/res/drawable/tab_widget_indicator_selector.xml
new file mode 100644
index 0000000..d06f757
--- /dev/null
+++ b/res/drawable/tab_widget_indicator_selector.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Non focused states -->
+    <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_holo" />
+
+    <!-- Focused states -->
+    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_focused_holo" />
+    <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_focused_holo" />
+
+    <!-- Pressed -->
+    <!--    Non focused states -->
+    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_holo" />
+    <item android:state_focused="false" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_holo" />
+
+    <!--    Focused states -->
+    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_focused_holo" />
+    <item android:state_focused="true" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_focused_holo" />
+</selector>
diff --git a/res/drawable/uninstall_target_selector.xml b/res/drawable/uninstall_target_selector.xml
new file mode 100644
index 0000000..229942e
--- /dev/null
+++ b/res/drawable/uninstall_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_launcher_trashcan_normal_holo"  />
+    <item android:drawable="@drawable/ic_launcher_trashcan_active_holo"  />
+</transition>
diff --git a/res/drawable/wallpaper_button.xml b/res/drawable/wallpaper_button.xml
new file mode 100644
index 0000000..72da99d
--- /dev/null
+++ b/res/drawable/wallpaper_button.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_wallpaper_pressed" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_wallpaper_pressed" />
+    <item android:drawable="@drawable/ic_wallpaper" />
+</selector>
diff --git a/res/drawable/wallpaper_tile_fg.xml b/res/drawable/wallpaper_tile_fg.xml
new file mode 100644
index 0000000..c299f32
--- /dev/null
+++ b/res/drawable/wallpaper_tile_fg.xml
@@ -0,0 +1,23 @@
+<?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" android:drawable="@drawable/tile_picker_selected" />
+    <item android:state_focused="true" android:drawable="@drawable/tile_picker_focused" />
+    <item android:state_pressed="true" android:drawable="@drawable/tile_picker_pressed" />
+    <item android:state_selected="true" android:drawable="@drawable/tile_picker_selected" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/widget_button.xml b/res/drawable/widget_button.xml
new file mode 100644
index 0000000..6936c87
--- /dev/null
+++ b/res/drawable/widget_button.xml
@@ -0,0 +1,21 @@
+<?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_focused="true" android:drawable="@drawable/ic_widget_pressed" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_widget_pressed" />
+    <item android:drawable="@drawable/ic_widget" />
+</selector>
diff --git a/res/layout-land/all_apps_cling.xml b/res/layout-land/all_apps_cling.xml
new file mode 100644
index 0000000..820f00a
--- /dev/null
+++ b/res/layout-land/all_apps_cling.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="all_apps_landscape">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="40dp"
+        android:layout_marginTop="40dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/all_apps_cling_title"
+                android:text="@string/all_apps_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/all_apps_cling_add_item"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/all_apps_cling_add_item" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="10dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/first_run_cling.xml b/res/layout-land/first_run_cling.xml
new file mode 100644
index 0000000..9baee64
--- /dev/null
+++ b/res/layout-land/first_run_cling.xml
@@ -0,0 +1,97 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    launcher:drawIdentifier="first_run_portrait">
+    <FrameLayout 
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:id="@+id/bubble_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="100dp"
+            android:layout_marginRight="100dp"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingAltTitleText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="10dp"
+                android:text="@string/first_run_cling_title"
+                android:textColor="#FFFFFFFF"
+                android:textSize="30sp"
+                android:gravity="center" />
+            <TextView
+                style="@style/ClingAltTitleText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/first_run_cling_description"
+                android:textColor="#80000000"
+                android:textSize="16sp"
+                android:gravity="center" />
+        </LinearLayout>
+        <TextView
+            style="@style/ClingHintText"
+            android:id="@+id/search_bar_hint"
+            android:layout_width="160dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|end"
+            android:layout_marginEnd="10dp"
+            android:layout_marginTop="65dp"
+            android:visibility="gone"
+            android:drawableTop="@drawable/cling_arrow_up"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_search_bar_hint" />
+        <TextView
+            style="@style/ClingHintText"
+            android:id="@+id/custom_content_hint"
+            android:layout_width="160dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="100dp"
+            android:visibility="gone"
+            android:drawableStart="@drawable/cling_arrow_left"
+            android:drawablePadding="10dp"
+            android:text="@string/first_run_cling_custom_content_hint" />
+        <TextView
+            style="@style/ClingHintText"
+            android:layout_width="160dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|end"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="85dp"
+            android:drawableEnd="@drawable/cling_arrow_right"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_create_screens_hint" />
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="20dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissFirstRunCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/folder_cling.xml b/res/layout-land/folder_cling.xml
new file mode 100644
index 0000000..86286d7
--- /dev/null
+++ b/res/layout-land/folder_cling.xml
@@ -0,0 +1,64 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="folder_portrait">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="15dp"
+        android:layout_marginEnd="15dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:paddingTop="20dp"
+                android:paddingBottom="20dp"
+                android:orientation="vertical"
+                android:background="@drawable/cling">
+                <TextView
+                    style="@style/ClingTitleText"
+                    android:id="@+id/folder_cling_title"
+                    android:text="@string/folder_cling_title" />
+                <TextView
+                    style="@style/ClingText"
+                    android:id="@+id/folder_cling_create_folder"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/folder_cling_create_folder" />
+            </LinearLayout>
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:src="@drawable/cling_arrow_down" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="20dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
new file mode 100644
index 0000000..77ea2e9
--- /dev/null
+++ b/res/layout-land/launcher.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    android:id="@+id/launcher"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/workspace_bg">
+
+    <com.android.launcher3.DragLayer
+        android:id="@+id/drag_layer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true">
+
+        <!-- The workspace contains 5 screens of cells -->
+        <com.android.launcher3.Workspace
+            android:id="@+id/workspace"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+            launcher:pageSpacing="@dimen/workspace_page_spacing"
+            launcher:pageIndicator="@id/page_indicator" />
+
+        <include layout="@layout/hotseat"
+            android:id="@+id/hotseat"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="end" />
+
+        <include
+            android:id="@+id/qsb_bar"
+            layout="@layout/qsb_bar" />
+
+        <include layout="@layout/overview_panel"
+            android:id="@+id/overview_panel"
+            android:visibility="gone" />
+
+        <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+             that it is still visible during the transition to AllApps and doesn't overlay on
+             top of that view. -->
+        <com.android.launcher3.ScrimView
+            android:id="@+id/cling_scrim"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/first_run_cling"
+            android:id="@+id/first_run_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/workspace_cling"
+            android:id="@+id/workspace_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
+        <include layout="@layout/folder_cling"
+            android:id="@+id/folder_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
+        <!-- TODO: Fix
+        <com.android.launcher3.DrawableStateProxyView
+            android:id="@+id/voice_button_proxy"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_gravity="top|start"
+            android:layout_marginTop="64dp"
+            android:clickable="true"
+            android:onClick="onClickVoiceButton"
+            android:importantForAccessibility="no"
+            launcher:sourceViewId="@+id/voice_button" />
+            -->
+
+        <include layout="@layout/apps_customize_pane"
+            android:id="@+id/apps_customize_pane"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="invisible" />
+    </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-land/search_bar.xml b/res/layout-land/search_bar.xml
new file mode 100644
index 0000000..d56e380
--- /dev/null
+++ b/res/layout-land/search_bar.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+   <!-- Global search icon -->
+   <com.android.launcher3.HolographicImageView
+        style="@style/SearchButton"
+        android:id="@+id/search_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:scaleType="center"
+        android:src="@drawable/ic_home_search_normal_holo"
+        android:adjustViewBounds="true"
+        android:onClick="onClickSearchButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_search_button" />
+
+    <!-- Voice search icon -->
+    <com.android.launcher3.HolographicImageView
+        style="@style/SearchButton"
+        android:id="@+id/voice_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:scaleType="center"
+        android:src="@drawable/ic_home_voice_search_holo"
+        android:adjustViewBounds="true"
+        android:onClick="onClickVoiceButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_voice_search_button" />
+</LinearLayout>
diff --git a/res/layout-land/workspace_cling.xml b/res/layout-land/workspace_cling.xml
new file mode 100644
index 0000000..db33db0
--- /dev/null
+++ b/res/layout-land/workspace_cling.xml
@@ -0,0 +1,69 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    launcher:drawIdentifier="workspace_portrait">
+    <FrameLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="25dp"
+        android:layout_marginEnd="25dp"
+        android:layout_marginTop="310dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:orientation="vertical">
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:src="@drawable/cling_arrow_up" />
+            <LinearLayout
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:paddingTop="20dp"
+                android:paddingBottom="20dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:background="@drawable/cling">
+                <TextView
+                    style="@style/ClingTitleText"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/workspace_cling_title" />
+                <TextView
+                    style="@style/ClingText"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/workspace_cling_move_item" />
+            </LinearLayout>
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="20dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissWorkspaceCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/all_apps_cling.xml b/res/layout-port/all_apps_cling.xml
new file mode 100644
index 0000000..62284ec
--- /dev/null
+++ b/res/layout-port/all_apps_cling.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="all_apps_portrait">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="20dp"
+        android:layout_marginTop="20dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/all_apps_cling_title"
+                android:text="@string/all_apps_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/all_apps_cling_add_item"
+                android:layout_width="285dp"
+                android:layout_height="wrap_content"
+                android:text="@string/all_apps_cling_add_item" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="10dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/first_run_cling.xml b/res/layout-port/first_run_cling.xml
new file mode 100644
index 0000000..4830e5d
--- /dev/null
+++ b/res/layout-port/first_run_cling.xml
@@ -0,0 +1,102 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    launcher:drawIdentifier="first_run_portrait">
+    <FrameLayout 
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:id="@+id/bubble_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="100dp"
+            android:layout_marginRight="100dp"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingAltTitleText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="10dp"
+                android:text="@string/first_run_cling_title"
+                android:textColor="#FFFFFFFF"
+                android:textSize="30sp"
+                android:gravity="center" />
+            <TextView
+                style="@style/ClingAltTitleText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/first_run_cling_description"
+                android:textColor="#80000000"
+                android:textSize="16sp"
+                android:gravity="center" />
+        </LinearLayout>
+        <TextView
+            style="@style/ClingHintText"
+            android:id="@+id/search_bar_hint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|end"
+            android:layout_marginEnd="10dp"
+            android:layout_marginTop="65dp"
+            android:gravity="center_horizontal"
+            android:maxWidth="160dp"
+            android:visibility="gone"
+            android:drawableTop="@drawable/cling_arrow_up"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_search_bar_hint" />
+        <TextView
+            style="@style/ClingHintText"
+            android:id="@+id/custom_content_hint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|start"
+            android:layout_marginStart="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginTop="100dp"
+            android:maxWidth="160dp"
+            android:visibility="gone"
+            android:drawableStart="@drawable/cling_arrow_start"
+            android:drawablePadding="10dp"
+            android:text="@string/first_run_cling_custom_content_hint" />
+        <TextView
+            style="@style/ClingHintText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|end"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="85dp"
+            android:maxWidth="180dp"
+            android:drawableEnd="@drawable/cling_arrow_end"
+            android:drawablePadding="5dp"
+            android:text="@string/first_run_cling_create_screens_hint" />
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="20dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissFirstRunCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/folder_cling.xml b/res/layout-port/folder_cling.xml
new file mode 100644
index 0000000..86286d7
--- /dev/null
+++ b/res/layout-port/folder_cling.xml
@@ -0,0 +1,64 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="folder_portrait">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="15dp"
+        android:layout_marginEnd="15dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:paddingTop="20dp"
+                android:paddingBottom="20dp"
+                android:orientation="vertical"
+                android:background="@drawable/cling">
+                <TextView
+                    style="@style/ClingTitleText"
+                    android:id="@+id/folder_cling_title"
+                    android:text="@string/folder_cling_title" />
+                <TextView
+                    style="@style/ClingText"
+                    android:id="@+id/folder_cling_create_folder"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/folder_cling_create_folder" />
+            </LinearLayout>
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:src="@drawable/cling_arrow_down" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:layout_marginBottom="15dp"
+        android:layout_marginEnd="20dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
new file mode 100644
index 0000000..6fbf7c7
--- /dev/null
+++ b/res/layout-port/launcher.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    android:id="@+id/launcher"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/workspace_bg">
+
+    <com.android.launcher3.DragLayer
+        android:id="@+id/drag_layer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <!-- The workspace contains 5 screens of cells -->
+        <com.android.launcher3.Workspace
+            android:id="@+id/workspace"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+            launcher:pageSpacing="@dimen/workspace_page_spacing"
+            launcher:pageIndicator="@id/page_indicator">
+        </com.android.launcher3.Workspace>
+
+        <include layout="@layout/hotseat"
+            android:id="@+id/hotseat"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+        <include layout="@layout/overview_panel"
+            android:id="@+id/overview_panel"
+            android:visibility="gone" />
+
+        <!-- Keep these behind the workspace so that they are not visible when
+             we go into AllApps -->
+        <include
+            android:id="@+id/page_indicator"
+            layout="@layout/page_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal" />
+
+        <include
+            android:id="@+id/qsb_bar"
+            layout="@layout/qsb_bar" />
+
+        <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+             that it is still visible during the transition to AllApps and doesn't overlay on
+             top of that view. -->
+        <com.android.launcher3.ScrimView
+            android:id="@+id/cling_scrim"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/first_run_cling"
+            android:id="@+id/first_run_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/workspace_cling"
+            android:id="@+id/workspace_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/folder_cling"
+            android:id="@+id/folder_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
+        <!-- This is the search bar voice button proxy view.  It allows us to have a larger
+             touch target than the microphone constrained by the search bar bounds. -->
+        <com.android.launcher3.DrawableStateProxyView
+            android:id="@+id/voice_button_proxy"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_gravity="top|end"
+            android:clickable="true"
+            android:onClick="onClickVoiceButton"
+            android:importantForAccessibility="no"
+            launcher:sourceViewId="@+id/voice_button" />
+
+        <include layout="@layout/apps_customize_pane"
+            android:id="@+id/apps_customize_pane"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="invisible" />
+    </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-port/search_bar.xml b/res/layout-port/search_bar.xml
new file mode 100644
index 0000000..1c96ca3
--- /dev/null
+++ b/res/layout-port/search_bar.xml
@@ -0,0 +1,73 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    style="@style/SearchDropTargetBar"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/search_frame">
+   <!-- Global search icon -->
+   <com.android.launcher3.HolographicLinearLayout
+        style="@style/SearchButton"
+        launcher:sourceImageViewId="@+id/search_button"
+        android:id="@+id/search_button_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:layout_toStartOf="@+id/voice_button_container"
+        android:paddingStart="8dp"
+        android:onClick="onClickSearchButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_search_button">
+       <ImageView
+            android:id="@+id/search_button"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_home_search_normal_holo"
+            android:adjustViewBounds="true" />
+    </com.android.launcher3.HolographicLinearLayout>
+
+    <!-- Voice search icon -->
+    <com.android.launcher3.HolographicLinearLayout
+        style="@style/SearchButton"
+        launcher:sourceImageViewId="@+id/voice_button"
+        android:id="@+id/voice_button_container"
+        android:layout_width="@dimen/app_icon_size"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_centerVertical="true"
+        android:layout_alignParentEnd="true"
+        android:paddingEnd="8dp"
+        android:onClick="onClickVoiceButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_voice_search_button">
+        <ImageView
+            android:id="@+id/voice_button"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_home_voice_search_holo"
+            android:adjustViewBounds="true" />
+    </com.android.launcher3.HolographicLinearLayout>
+</RelativeLayout>
diff --git a/res/layout-port/workspace_cling.xml b/res/layout-port/workspace_cling.xml
new file mode 100644
index 0000000..9c000cb
--- /dev/null
+++ b/res/layout-port/workspace_cling.xml
@@ -0,0 +1,107 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    launcher:drawIdentifier="workspace_portrait">
+    <FrameLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:layout_marginStart="25dp"
+            android:layout_marginEnd="25dp"
+            android:layout_marginTop="30dp"
+            android:orientation="vertical">
+            <LinearLayout
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:paddingTop="20dp"
+                android:paddingBottom="20dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:background="@drawable/cling">
+                <TextView
+                    style="@style/ClingTitleText"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/workspace_cling_title" />
+                <TextView
+                    style="@style/ClingText"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/workspace_cling_move_item" />
+            </LinearLayout>
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:src="@drawable/cling_arrow_down" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/focused_hotseat_app_bubble"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|right"
+            android:layout_marginRight="25dp"
+            android:layout_marginBottom="90dp"
+            android:orientation="vertical"
+            android:visibility="gone">
+            <LinearLayout
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:paddingTop="20dp"
+                android:paddingBottom="20dp"
+                android:layout_width="240dp"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:background="@drawable/cling">
+                <TextView
+                    android:id="@+id/focused_hotseat_app_title"
+                    style="@style/ClingTitleText"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+                <TextView
+                    android:id="@+id/focused_hotseat_app_description"
+                    style="@style/ClingText"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+            </LinearLayout>
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="right"
+                android:layout_marginRight="80dp"
+                android:src="@drawable/cling_arrow_down" />
+        </LinearLayout>
+    </FrameLayout>
+
+    <Button
+        style="@style/ClingButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="15dp"
+        android:layout_marginLeft="20dp"
+        android:layout_gravity="bottom|left"
+        android:onClick="dismissWorkspaceCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw600dp-port/all_apps_cling.xml b/res/layout-sw600dp-port/all_apps_cling.xml
new file mode 100644
index 0000000..cf65e41
--- /dev/null
+++ b/res/layout-sw600dp-port/all_apps_cling.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="all_apps_portrait">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="20dp"
+        android:layout_marginTop="20dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/all_apps_cling_title"
+                android:text="@string/all_apps_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/all_apps_cling_add_item"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/all_apps_cling_add_item" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:minWidth="168dp"
+        android:textSize="24sp"
+        android:layout_marginTop="235dp"
+        android:layout_marginEnd="36dp"
+        android:layout_gravity="top|end"
+        android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw600dp-port/folder_cling.xml b/res/layout-sw600dp-port/folder_cling.xml
new file mode 100644
index 0000000..87086cb
--- /dev/null
+++ b/res/layout-sw600dp-port/folder_cling.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="folder_portrait">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="10dp"
+        android:layout_marginTop="@dimen/folderClingMarginTop">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/folder_cling_title"
+                android:text="@string/folder_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/folder_cling_create_folder"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/folder_cling_create_folder" />
+        </LinearLayout>
+    </FrameLayout>
+    <Button
+        style="@style/ClingButton"
+        android:id="@+id/cling_dismiss"
+        android:minWidth="168dp"
+        android:textSize="24sp"
+        android:layout_marginBottom="27dp"
+        android:layout_marginEnd="36dp"
+        android:layout_gravity="bottom|end"
+        android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp-port/folder_cling.xml b/res/layout-sw720dp-port/folder_cling.xml
new file mode 100644
index 0000000..40d4e20
--- /dev/null
+++ b/res/layout-sw720dp-port/folder_cling.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="folder_large">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/cling_text_block_offset_x"
+        android:layout_marginTop="@dimen/cling_text_block_offset_y">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/folder_cling_title"
+                android:text="@string/folder_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/folder_cling_create_folder"
+                android:layout_width="480dp"
+                android:layout_height="wrap_content"
+                android:text="@string/folder_cling_create_folder" />
+            <Button
+                style="@style/ClingButton"
+                android:id="@+id/cling_dismiss"
+                android:layout_marginTop="15dp"
+                android:onClick="dismissFolderCling" />
+        </LinearLayout>
+    </FrameLayout>
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp/all_apps_cling.xml b/res/layout-sw720dp/all_apps_cling.xml
new file mode 100644
index 0000000..824d84f
--- /dev/null
+++ b/res/layout-sw720dp/all_apps_cling.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    launcher:drawIdentifier="all_apps_large">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/cling_text_block_offset_x"
+        android:layout_marginTop="@dimen/cling_text_block_offset_y">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                style="@style/ClingTitleText"
+                android:id="@+id/all_apps_cling_title"
+                android:text="@string/all_apps_cling_title" />
+            <TextView
+                style="@style/ClingText"
+                android:id="@+id/all_apps_cling_add_item"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/all_apps_cling_add_item" />
+            <Button
+                style="@style/ClingButton"
+                android:id="@+id/cling_dismiss"
+                android:layout_marginTop="15dp"
+                android:onClick="dismissAllAppsCling" />
+        </LinearLayout>
+    </FrameLayout>
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp/external_widget_drop_list_item.xml b/res/layout-sw720dp/external_widget_drop_list_item.xml
new file mode 100644
index 0000000..48e333b
--- /dev/null
+++ b/res/layout-sw720dp/external_widget_drop_list_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:layout_width="match_parent"
+    android:layout_height="64dp">
+    <ImageView
+        android:id="@+id/provider_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="20dp"
+        android:maxWidth="32dp"
+        android:maxHeight="32dp"
+        android:scaleType="fitCenter"
+        android:src="@mipmap/ic_launcher_application" />
+    <TextView
+        android:id="@+id/provider"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="5dp"
+        android:gravity="center_vertical"
+        android:textSize="18sp" />
+</LinearLayout>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
new file mode 100644
index 0000000..951e63a
--- /dev/null
+++ b/res/layout-sw720dp/launcher.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    android:id="@+id/launcher"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/workspace_bg">
+
+    <com.android.launcher3.DragLayer
+        android:id="@+id/drag_layer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true">
+
+        <!-- The workspace contains 5 screens of cells -->
+        <com.android.launcher3.Workspace
+            android:id="@+id/workspace"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+            launcher:pageSpacing="@dimen/workspace_page_spacing"
+            launcher:pageIndicator="@id/page_indicator">
+        </com.android.launcher3.Workspace>
+
+        <include layout="@layout/hotseat"
+            android:id="@+id/hotseat"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+        <include
+            android:id="@+id/qsb_bar"
+            layout="@layout/qsb_bar" />
+
+        <include layout="@layout/overview_panel"
+            android:id="@+id/overview_panel"
+            android:visibility="gone" />
+
+        <!-- Keep these behind the workspace so that they are not visible when
+             we go into AllApps -->
+        <include
+            android:id="@+id/page_indicator"
+            layout="@layout/page_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal" />
+
+        <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+             that it is still visible during the transition to AllApps and doesn't overlay on
+             top of that view. -->
+        <include layout="@layout/first_run_cling"
+            android:id="@+id/first_run_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+        <include layout="@layout/workspace_cling"
+            android:id="@+id/workspace_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
+        <include layout="@layout/folder_cling"
+            android:id="@+id/folder_cling"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
+        <com.android.launcher3.DrawableStateProxyView
+            android:id="@+id/voice_button_proxy"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_gravity="top|end"
+            android:clickable="true"
+            android:onClick="onClickVoiceButton"
+            android:importantForAccessibility="no"
+            launcher:sourceViewId="@+id/voice_button" />
+
+        <include layout="@layout/apps_customize_pane"
+            android:id="@+id/apps_customize_pane"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="invisible" />
+    </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-sw720dp/market_button.xml b/res/layout-sw720dp/market_button.xml
new file mode 100644
index 0000000..f1e9959
--- /dev/null
+++ b/res/layout-sw720dp/market_button.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/MarketButton"
+    android:onClick="onClickAppMarketButton"
+    android:gravity="center"
+    android:paddingStart="32dp"
+    android:paddingEnd="32dp"
+    android:drawablePadding="10dp"
+    android:background="@drawable/tab_widget_indicator_selector"
+    android:text="@string/market"
+    android:contentDescription="@string/market"
+    android:textColor="@color/workspace_all_apps_and_delete_zone_text_color"
+    android:textStyle="bold"
+    android:textSize="14sp"
+    android:textAllCaps="true"
+    android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
+    android:shadowDx="0.0"
+    android:shadowDy="0.0"
+    android:shadowRadius="2.0"
+    android:focusable="true"
+    android:clickable="true" />
diff --git a/res/layout-sw720dp/search_bar.xml b/res/layout-sw720dp/search_bar.xml
new file mode 100644
index 0000000..69dd61a
--- /dev/null
+++ b/res/layout-sw720dp/search_bar.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    style="@style/SearchDropTargetBar"
+    android:background="@drawable/search_frame">
+   <!-- Global search icon -->
+   <com.android.launcher3.HolographicLinearLayout
+        style="@style/SearchButton"
+        launcher:sourceImageViewId="@+id/search_button"
+        android:id="@+id/search_button_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:layout_toStartOf="@+id/voice_button_container"
+        android:paddingStart="8dp"
+        android:onClick="onClickSearchButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_search_button">
+       <ImageView
+            android:id="@+id/search_button"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_home_search_normal_holo"
+            android:adjustViewBounds="true" />
+    </com.android.launcher3.HolographicLinearLayout>
+
+    <!-- Voice search icon -->
+    <com.android.launcher3.HolographicLinearLayout
+        style="@style/SearchButton"
+        launcher:sourceImageViewId="@+id/voice_button"
+        android:id="@+id/voice_button_container"
+        android:layout_width="@dimen/app_icon_size"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_centerVertical="true"
+        android:layout_alignParentEnd="true"
+        android:paddingEnd="8dp"
+        android:onClick="onClickVoiceButton"
+        android:focusable="true"
+        android:clickable="true"
+        android:contentDescription="@string/accessibility_voice_search_button">
+        <ImageView
+            android:id="@+id/voice_button"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_home_voice_search_holo"
+            android:adjustViewBounds="true" />
+    </com.android.launcher3.HolographicLinearLayout>
+</RelativeLayout>
diff --git a/res/layout/actionbar_set_wallpaper.xml b/res/layout/actionbar_set_wallpaper.xml
new file mode 100644
index 0000000..1622742
--- /dev/null
+++ b/res/layout/actionbar_set_wallpaper.xml
@@ -0,0 +1,34 @@
+<?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"
+    style="?android:actionButtonStyle"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <TextView style="?android:actionBarTabTextStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:paddingRight="20dp"
+        android:drawableLeft="@drawable/ic_actionbar_accept"
+        android:drawablePadding="8dp"
+        android:gravity="center_vertical"
+        android:text="@string/wallpaper_instructions" />
+</FrameLayout>
diff --git a/res/layout/add_list_item.xml b/res/layout/add_list_item.xml
new file mode 100644
index 0000000..0ae0113
--- /dev/null
+++ b/res/layout/add_list_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:drawablePadding="14dip"
+    android:paddingStart="15dip"
+    android:paddingEnd="15dip" />
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
new file mode 100644
index 0000000..1b9ea08
--- /dev/null
+++ b/res/layout/all_apps_button.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+   style="@style/WorkspaceIcon"
+   android:focusable="true"
+   android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/application.xml b/res/layout/application.xml
new file mode 100644
index 0000000..e4909dd
--- /dev/null
+++ b/res/layout/application.xml
@@ -0,0 +1,20 @@
+<?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.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
+   style="@style/WorkspaceIcon"
+   android:focusable="true"
+   android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/apps_customize_application.xml b/res/layout/apps_customize_application.xml
new file mode 100644
index 0000000..3b0fa6f
--- /dev/null
+++ b/res/layout/apps_customize_application.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<com.android.launcher3.PagedViewIcon
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    style="@style/WorkspaceIcon.AppsCustomize"
+
+    android:id="@+id/application_icon"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    
+    android:focusable="true"
+    android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
new file mode 100644
index 0000000..11a938f
--- /dev/null
+++ b/res/layout/apps_customize_pane.xml
@@ -0,0 +1,81 @@
+<?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.
+-->
+<com.android.launcher3.AppsCustomizeTabHost
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:background="#80FFFFFF">
+    <LinearLayout
+        android:id="@+id/apps_customize_content"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone">
+        <!-- The layout_width of the tab bar gets overriden to align the content
+             with the text in the tabs in AppsCustomizeTabHost. -->
+        <FrameLayout
+            android:id="@+id/tabs_container"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/apps_customize_tab_bar_height"
+            android:layout_marginTop="@dimen/apps_customize_tab_bar_margin_top"
+            android:layout_gravity="center_horizontal"
+            android:visibility="gone">
+            <com.android.launcher3.FocusOnlyTabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:gravity="start"
+                android:background="@drawable/tab_unselected_holo"
+                android:tabStripEnabled="false"
+                android:divider="@null" />
+            <include
+                android:id="@+id/market_button"
+                layout="@layout/market_button"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="end" />
+        </FrameLayout>
+        <FrameLayout
+            android:id="@android:id/tabcontent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <com.android.launcher3.AppsCustomizePagedView
+                android:id="@+id/apps_customize_pane_content"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginBottom="@dimen/apps_customize_page_indicator_offset"
+                launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
+                launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
+                launcher:clingFocusedX="@integer/apps_customize_cling_focused_x"
+                launcher:clingFocusedY="@integer/apps_customize_cling_focused_y"
+                launcher:maxGap="@dimen/workspace_max_gap"
+                launcher:pageIndicator="@+id/page_indicator" />
+            <FrameLayout
+                android:id="@+id/animation_buffer"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="#FF000000"
+                android:visibility="gone" />
+            <include
+                android:id="@+id/page_indicator"
+                layout="@layout/page_indicator"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|center_horizontal"
+                android:layout_marginBottom="@dimen/apps_customize_page_indicator_margin" />
+        </FrameLayout>
+    </LinearLayout>
+</com.android.launcher3.AppsCustomizeTabHost>
diff --git a/res/layout/apps_customize_progressbar.xml b/res/layout/apps_customize_progressbar.xml
new file mode 100644
index 0000000..6aa9099
--- /dev/null
+++ b/res/layout/apps_customize_progressbar.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<ProgressBar
+   xmlns:android="http://schemas.android.com/apk/res/android"
+   style="?android:attr/progressBarStyleLarge"
+   android:id="@+id/apps_customize_progress_bar"
+   android:layout_width="wrap_content"
+   android:layout_height="wrap_content"
+   android:layout_gravity="center" />
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml
new file mode 100644
index 0000000..f2d2342
--- /dev/null
+++ b/res/layout/apps_customize_widget.xml
@@ -0,0 +1,83 @@
+<?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.
+-->
+<com.android.launcher3.PagedViewWidget
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:orientation="vertical"
+
+    android:background="@drawable/focusable_view_bg"
+    android:focusable="true">
+
+    <!-- The preview of the widget or shortcut. -->
+    <com.android.launcher3.PagedViewWidgetImageView
+        android:id="@+id/widget_preview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="@dimen/app_widget_preview_padding_top"
+        android:paddingStart="@dimen/app_widget_preview_padding_left"
+        android:paddingEnd="@dimen/app_widget_preview_padding_right"
+        android:scaleType="matrix"
+        android:background="@drawable/screenpanel" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/app_widget_preview_label_margin_top"
+        android:layout_marginStart="@dimen/app_widget_preview_label_margin_left"
+        android:layout_marginEnd="@dimen/app_widget_preview_label_margin_right"
+        android:orientation="horizontal">
+        <!-- The name of the widget. -->
+        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/widget_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="start"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+
+            android:textColor="#FFFFFFFF"
+            android:textSize="13sp"
+            android:textAlignment="viewStart"
+            android:fontFamily="sans-serif-condensed"
+            android:shadowRadius="2.0"
+            android:shadowColor="#B0000000" />
+
+        <!-- The original dimensions of the widget (can't be the same text as above due to different
+             style. -->
+        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/widget_dims"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginStart="5dp"
+            android:layout_weight="0"
+            android:gravity="start"
+
+            android:textColor="#FFAAAAAA"
+            android:textSize="12sp"
+            android:fontFamily="sans-serif-condensed"
+            android:shadowRadius="2.0"
+            android:shadowColor="#B0000000" />
+    </LinearLayout>
+
+
+</com.android.launcher3.PagedViewWidget>
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
new file mode 100644
index 0000000..f5a9148
--- /dev/null
+++ b/res/layout/appwidget_error.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingTop="10dip"
+    android:paddingBottom="10dip"
+    android:paddingStart="20dip"
+    android:paddingEnd="20dip"
+    android:gravity="center"
+    android:background="@drawable/bg_appwidget_error"
+    android:textAppearance="?android:attr/textAppearanceMediumInverse"
+    android:textColor="@color/appwidget_error_color"
+    android:text="@string/gadget_error_text"
+    />
diff --git a/res/layout/custom_content_page_indicator_marker.xml b/res/layout/custom_content_page_indicator_marker.xml
new file mode 100644
index 0000000..8fe3f8f
--- /dev/null
+++ b/res/layout/custom_content_page_indicator_marker.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.
+-->
+<com.android.launcher3.PageIndicatorMarker
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="16dp"
+    android:layout_height="16dp"
+    android:layout_gravity="center_vertical">
+    <ImageView
+        android:id="@+id/inactive"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerInside"
+        android:src="@drawable/custom_content_page"
+        />
+    <ImageView
+        android:id="@+id/active"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerInside"
+        android:src="@drawable/custom_content_page"
+        android:alpha="0"
+        android:scaleX="0.5"
+        android:scaleY="0.5"
+        />
+</com.android.launcher3.PageIndicatorMarker>
diff --git a/res/layout/custom_workspace_cling.xml b/res/layout/custom_workspace_cling.xml
new file mode 100644
index 0000000..3744f2e
--- /dev/null
+++ b/res/layout/custom_workspace_cling.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- dummy layout, to be replaced in overlays -->
+<com.android.launcher3.Cling
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    launcher:drawIdentifier="workspace_custom">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="20dp"
+        android:textColor="@android:color/holo_blue_light"
+        android:text="@string/dummy_custom_cling_error_message"
+        android:gravity="start"
+        android:layout_gravity="bottom|start"
+    />
+</com.android.launcher3.Cling>
diff --git a/res/layout/drop_target_bar.xml b/res/layout/drop_target_bar.xml
new file mode 100644
index 0000000..f38a500
--- /dev/null
+++ b/res/layout/drop_target_bar.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/DropTargetButtonContainer"
+        android:layout_weight="1">
+        <!-- Delete target -->
+        <com.android.launcher3.DeleteDropTarget
+            style="@style/DropTargetButton"
+            android:id="@+id/delete_target_text"
+            android:text="@string/delete_zone_label_workspace"
+            android:drawableStart="@drawable/remove_target_selector" />
+    </FrameLayout>
+    <FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/DropTargetButtonContainer"
+        android:layout_weight="1">
+        <!-- Info target -->
+        <com.android.launcher3.InfoDropTarget
+            style="@style/DropTargetButton"
+            android:id="@+id/info_target_text"
+            android:text="@string/info_target_label"
+            android:drawableStart="@drawable/info_target_selector" />
+    </FrameLayout>
+</merge>
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
new file mode 100644
index 0000000..4405682
--- /dev/null
+++ b/res/layout/folder_icon.xml
@@ -0,0 +1,34 @@
+<?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.launcher3.FolderIcon
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:focusable="true"
+    android:background="@drawable/focusable_view_bg">
+    <ImageView
+        android:id="@+id/preview_background"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:antialias="true"
+        android:src="@drawable/portal_ring_inner_holo"/>
+    <com.android.launcher3.BubbleTextView
+        style="@style/WorkspaceIcon"
+        android:id="@+id/folder_icon_name" />
+</com.android.launcher3.FolderIcon>
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
new file mode 100644
index 0000000..dc9ed2a
--- /dev/null
+++ b/res/layout/hotseat.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<com.android.launcher3.Hotseat
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <com.android.launcher3.CellLayout
+        android:id="@+id/layout"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center" />
+</com.android.launcher3.Hotseat>
diff --git a/res/layout/market_button.xml b/res/layout/market_button.xml
new file mode 100644
index 0000000..4a718c3
--- /dev/null
+++ b/res/layout/market_button.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/MarketButton"
+    android:onClick="onClickAppMarketButton"
+    android:gravity="center"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
+    android:background="@drawable/tab_widget_indicator_selector"
+    android:contentDescription="@string/market"
+    android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
+    android:shadowDx="0.0"
+    android:shadowDy="0.0"
+    android:shadowRadius="2.0"
+    android:focusable="true"
+    android:clickable="true" />
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
new file mode 100644
index 0000000..e36004c
--- /dev/null
+++ b/res/layout/overview_panel.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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:layout_gravity="center_horizontal|bottom"
+    android:paddingBottom="@dimen/overview_panel_bottom_padding">
+
+    <TextView
+        android:id="@+id/wallpaper_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/wallpaper_button_text"
+        android:drawablePadding="4dp"
+        android:drawableTop="@drawable/wallpaper_button"
+        android:gravity="center_horizontal"
+        android:fontFamily="sans-serif-condensed"
+        android:textAllCaps="true"
+        android:textSize="12sp" />
+    <Space
+        android:layout_width="@dimen/overview_panel_buttonSpacing"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/widget_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/widget_button_text"
+        android:drawablePadding="4dp"
+        android:gravity="center_horizontal"
+        android:drawableTop="@drawable/widget_button"
+        android:fontFamily="sans-serif-condensed"
+        android:textAllCaps="true"
+        android:textSize="12sp"/>
+    <Space
+        android:layout_width="@dimen/overview_panel_buttonSpacing"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/settings_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/settings_button_text"
+        android:drawablePadding="4dp"
+        android:gravity="center_horizontal"
+        android:drawableTop="@drawable/setting_button"
+        android:fontFamily="sans-serif-condensed"
+        android:textAllCaps="true"
+        android:textSize="12sp" />
+</LinearLayout>
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
new file mode 100644
index 0000000..14eff75
--- /dev/null
+++ b/res/layout/page_indicator.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<com.android.launcher3.PageIndicator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:animateLayoutChanges="true"
+    launcher:windowSize="@integer/config_maxNumberOfPageIndicatorsToShow">
+</com.android.launcher3.PageIndicator>
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
new file mode 100644
index 0000000..7c0c389
--- /dev/null
+++ b/res/layout/page_indicator_marker.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.
+-->
+<com.android.launcher3.PageIndicatorMarker
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="16dp"
+    android:layout_height="16dp"
+    android:layout_gravity="center_vertical">
+    <ImageView
+        android:id="@+id/inactive"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerInside"
+        android:src="@drawable/ic_pageindicator_default"
+        />
+    <ImageView
+        android:id="@+id/active"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerInside"
+        android:src="@drawable/ic_pageindicator_current"
+        android:alpha="0"
+        android:scaleX="0.5"
+        android:scaleY="0.5"
+        />
+</com.android.launcher3.PageIndicatorMarker>
diff --git a/res/layout/qsb_bar.xml b/res/layout/qsb_bar.xml
new file mode 100644
index 0000000..030acf6
--- /dev/null
+++ b/res/layout/qsb_bar.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<com.android.launcher3.SearchDropTargetBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/QSBBar"
+    android:focusable="false"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Drag specific targets container -->
+    <LinearLayout
+        style="@style/SearchDropTargetBar"
+        android:id="@+id/drag_target_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center">
+
+        <include
+            layout="@layout/drop_target_bar" />
+    </LinearLayout>
+</com.android.launcher3.SearchDropTargetBar>
diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml
new file mode 100644
index 0000000..21a335c
--- /dev/null
+++ b/res/layout/rename_folder.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="20dip"
+    android:orientation="vertical">
+
+    <TextView 
+        android:id="@+id/label"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/rename_folder_label"
+        android:gravity="start"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+            
+    <EditText
+        android:id="@+id/folder_name"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:scrollHorizontally="true"
+        android:autoText="false"
+        android:capitalize="none"
+        android:gravity="fill_horizontal"
+        android:maxLength="30"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/res/layout/tab_widget_indicator.xml b/res/layout/tab_widget_indicator.xml
new file mode 100644
index 0000000..de7c50e
--- /dev/null
+++ b/res/layout/tab_widget_indicator.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<com.android.launcher3.AccessibleTabView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/TabIndicator.AppsCustomize" />
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
new file mode 100644
index 0000000..5d5f33b
--- /dev/null
+++ b/res/layout/user_folder.xml
@@ -0,0 +1,54 @@
+<?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.launcher3.Folder
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:background="@drawable/portal_container_holo">
+
+    <ScrollView
+        android:id="@+id/scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+      <com.android.launcher3.CellLayout
+          android:id="@+id/folder_content"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:cacheColorHint="#ff333333"
+          android:hapticFeedbackEnabled="false" />
+    </ScrollView>
+
+    <com.android.launcher3.FolderEditText
+        android:id="@+id/folder_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:paddingTop="@dimen/folder_name_padding"
+        android:paddingBottom="@dimen/folder_name_padding"
+        android:background="#00000000"
+        android:hint="@string/folder_hint_text"
+        android:textSize="14sp"
+        android:textColor="#ff777777"
+        android:textColorHighlight="#ffCCCCCC"
+        android:textCursorDrawable="@null"
+        android:gravity="center_horizontal"
+        android:singleLine="true"
+        android:imeOptions="flagNoExtractUi"
+        android:fontFamily="sans-serif-condensed"/>
+</com.android.launcher3.Folder>
diff --git a/res/layout/wallpaper_cropper.xml b/res/layout/wallpaper_cropper.xml
new file mode 100644
index 0000000..3a3d98a
--- /dev/null
+++ b/res/layout/wallpaper_cropper.xml
@@ -0,0 +1,39 @@
+<?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:id="@+id/wallpaper_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.launcher3.CropView
+        android:id="@+id/cropView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ProgressBar
+        android:id="@+id/loading"
+        style="@android:style/Widget.Holo.ProgressBar.Large"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        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..c91cc7e
--- /dev/null
+++ b/res/layout/wallpaper_picker.xml
@@ -0,0 +1,77 @@
+<?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.launcher3.WallpaperRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/wallpaper_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.launcher3.CropView
+        android:id="@+id/cropView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ProgressBar
+        android:id="@+id/loading"
+        style="@android:style/Widget.Holo.ProgressBar.Large"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:indeterminate="true"
+        android:indeterminateOnly="true"
+        android:background="@android:color/transparent" />
+    <LinearLayout
+        android:id="@+id/wallpaper_strip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="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>
+</com.android.launcher3.WallpaperRootView>
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..ae3c43d
--- /dev/null
+++ b/res/layout/wallpaper_picker_image_picker_item.xml
@@ -0,0 +1,42 @@
+<?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.launcher3.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:background="@drawable/wallpaper_tile_fg"
+    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.launcher3.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_item.xml b/res/layout/wallpaper_picker_item.xml
new file mode 100644
index 0000000..0ac8f97
--- /dev/null
+++ b/res/layout/wallpaper_picker_item.xml
@@ -0,0 +1,30 @@
+<?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.launcher3.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:background="@drawable/wallpaper_tile_fg"
+    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.launcher3.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..29fdb1b
--- /dev/null
+++ b/res/layout/wallpaper_picker_live_wallpaper_item.xml
@@ -0,0 +1,49 @@
+<?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.launcher3.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:background="@drawable/wallpaper_tile_fg"
+    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.launcher3.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..68661bc
--- /dev/null
+++ b/res/layout/wallpaper_picker_third_party_item.xml
@@ -0,0 +1,40 @@
+<?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.launcher3.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:background="@drawable/wallpaper_tile_fg"
+    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.launcher3.CheckableFrameLayout>
diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
new file mode 100644
index 0000000..855cf39
--- /dev/null
+++ b/res/layout/workspace_screen.xml
@@ -0,0 +1,25 @@
+<?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.launcher3.CellLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:hapticFeedbackEnabled="false"
+
+    launcher:maxGap="@dimen/workspace_max_gap" />
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_application.png b/res/mipmap-hdpi/ic_launcher_application.png
new file mode 100644
index 0000000..b9aa101
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
new file mode 100644
index 0000000..b556d7a
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_home.png
Binary files differ
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_application.png b/res/mipmap-mdpi/ic_launcher_application.png
new file mode 100644
index 0000000..4771b85
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
new file mode 100644
index 0000000..961bb7d
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_home.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_application.png b/res/mipmap-xhdpi/ic_launcher_application.png
new file mode 100644
index 0000000..932f0f7
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
new file mode 100644
index 0000000..46ec2b7
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_home.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_application.png b/res/mipmap-xxhdpi/ic_launcher_application.png
new file mode 100644
index 0000000..7fc739a
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
new file mode 100644
index 0000000..d2975a3
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_home.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-land/strings.xml b/res/values-af-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-af-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..e167189
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Tuis"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android-kernprogramme"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Stel muurpapier"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d gekies"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d gekies"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d gekies"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Muurpapier %1$d van %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Het <xliff:g id="LABEL">%1$s</xliff:g> gekies"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Vee uit"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Kies prent"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Muurpapiere"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Snoei muurpapier"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Legstukke"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Legstukke"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Wys Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Raak en hou om \'n legstuk op te tel."</string>
+    <string name="market" msgid="2619650989819296998">"Winkel"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Kan nie item op hierdie Tuisskerm laat los nie."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Kies legstuk om te skep"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Vouernaam"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Hernoem vouer"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Kanselleer"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Voeg by Tuisskerm"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Programme"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Kortpaaie"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Legstukke"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Niks meer spasie op jou Tuisskerms nie."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Niks meer plek op die warmlaai nie."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Hierdie legstuk is te groot vir die warmlaai."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" is geskep."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" is verwyder."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" bestaan reeds."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Kies kortpad"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Kies program"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Programme"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Tuis"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Verwyder"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deïnstalleer"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Verwyder"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deïnstalleer"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Programinligting"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Soek"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Stemsoektog"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programme"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Verwyder"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Deïnstalleer opdatering"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Deïnstalleer program"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Programbesonderhede"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 program gekies"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 legstuk gekies"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 vouer gekies"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 kortpad gekies"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installeer kortpaaie"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"deïnstalleer kortpaaie"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Laat die program toe om kortpaaie te verwyder sonder gebruikerinmenging."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lees Tuis-instellings en -kortpaaie"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Laat die program toe om die instellings en kortpaaie in Tuis te lees."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"skryf Tuis-instellings en -kortpaaie"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Laat die program toe om die instellings en kortpaaie in Tuis te verander."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Kon nie legstuk laai nie"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dit is \'n stelselprogram en kan nie gedeïnstalleer word nie."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Vuurpyllanseerder"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Naamlose vouer"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Tuisskerm %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Bladsy %1$d van %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Tuisskerm %1$d van %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Programme-bladsy %1$d van %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Legstukke-bladsy %1$d van %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Welkom!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Maak jouself tuis."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Skep meer skerms vir programme en vouers"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiseer jou spasie"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Raak en hou agtergrond om muurpapier, legstukke en instellings te bestuur."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Kies \'n paar programme"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Om \'n program by jou Tuisskerm te voeg, raak en hou dit."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Hier\'s \'n vouer"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Om een soos dié te skep, raak en hou \'n program en skuif dit dan oor \'n ander een."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Vouer oopgemaak, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Raak om vouer toe te maak"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Raak om hernoem te stoor"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Vouer is gesluit"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Vouer hernoem na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Legstukke"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Muurpapiere"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Instellings"</string>
+</resources>
diff --git a/res/values-am-land/strings.xml b/res/values-am-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-am-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..6e8aaa4
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"ማስጀመሪያ3"</string>
+    <string name="home" msgid="7658288663002113681">"መነሻ"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android ዋና መተግበሪያዎች"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"የግድግዳ ወረቀት አዘጋጅ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d ተመርጧል"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d ተመርጧል"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d ተመርጧል"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"የግድግዳ ወረቀት %1$d ከ%2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> ተመርጧል"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"ሰርዝ"</string>
+    <string name="pick_image" msgid="1272073934062909527">"ምስል ምረጥ"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"የግድግዳ ወረቀቶች"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"የግድግዳ ወረቀት ከርክም"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"መተግበሪያ አልተጫነም።"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ፍርግሞች"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ፍርግሞች"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"ማህደረ ማስታወሻ አሳይ"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"ፍርግም ለማንሳት ይንኩ እና ይያዙት"</string>
+    <string name="market" msgid="2619650989819296998">"ግዛ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"ንጥሉን እዚህ የመነሻ ማያ ገጽ ላይ ማኖር አልተቻለም።"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ለመፍጠር መግብር ይምረጡ"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"አቃፊ ስም"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"አቃፊ ዳግም ሰይም"</string>
+    <string name="rename_action" msgid="5559600076028658757">"እሺ"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"ተው"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"ወደ መነሻ ማያ ገጽ ያክሉ"</string>
+    <string name="group_applications" msgid="3797214114206693605">"መተግበሪያዎች"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"አቋራጮች"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ፍርግሞች"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"የመነሻ ማያ ገጾችዎ ላይ ተጨማሪ ቦታ የለም።"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"በመትከያ ቦታው ላይ ተጨማሪ ቦታ የለም።"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"ይህ ፍርግም ለመትከያ ቦታው በጣም ትልቅ ነው።"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» ተፈጥሯል።"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» ተወግዶ ነበር።"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» አስቀድሞ አለ።"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"አቋራጭ ይምረጡ"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"መተግበሪያ ይምረጡ"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"መተግበሪያዎች"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"መነሻ"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"አስወግድ"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"አራግፍ"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"አስወግድ"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"አራግፍ"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"የመተግበሪያ መረጃ"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"ፍለጋ"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"የድምፅ ፍለጋ"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"መተግበሪያዎች"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"አስወግድ"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ዝማኔ አራግፍ"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"መተግበሪያ አራግፍ"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"የመተግበሪያ ዝርዝሮች"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 መተግበሪያ ተመርጧል"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ፍርግም ተመርጧል"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 አቃፊ ተመርጧል"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 አቋራጭ ተመርጧል"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"አቋራጮችን ይጭናል"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"አቋራጮችን ያራግፋል"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"መተግበሪያው አቋራጮችን ያለተጠቃሚ ጣልቃ ገብነት እንዲያስወግድ ያስችለዋል።"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"የመነሻ ቅንብሮች እና አቋራጮችን ያነባል"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"መተግበሪያው በመነሻ ውስጥ ያሉ ቅንብሮችን እና አቋራጮችን እንዲያነብ ያስችለዋል።"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"የመነሻ ቅንብሮችን እና አቋራጮችን ይጽፋል"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"መተግብሪያው ቅንብሮችን እና አቋራጮችን በመነሻ ውስጥ እንዲቀይራቸው ያስችለዋል።"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"ፍርግም የመጫን ችግር"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"ይህ የስርዓት መተግበሪያ ነው እና ማራገፍ አይቻልም።"</string>
+    <string name="dream_name" msgid="1530253749244328964">"የሮኬት ማስጀመሪያ"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"ስም-አልባ አቃፊ"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"መነሻ ማያ ገጽ %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"ገጽ %1$d ከ%2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"መነሻ ማያ ገጽ %1$d ከ%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"የመተግበሪያዎች ገጽ %1$d ከ%2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"የመግብሮች ገጽ %1$d ከ%2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"እንኳን ደህና መጡ!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"ልክ እቤትዎ እንዳሉ ሆነው ዘና ይበሉ።"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"ለመተግበሪያዎች እና አቃፊዎች ተጨማሪ ማያ ገጾችን ይፍጠሩ"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"ቦታዎን ያደራጁ"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"የግድግዳ ወረቀት፣ ምግብሮችን እና ቅንብሮችን ለማቀናበር ጀርባውን ይንኩ እና ይያዙት።"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"አንዳንድ መተግበሪያዎችን ይምረጡ"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"አንድ መተግበሪያ ወደ መነሻ ማያ ገጽዎ ለማከል ይንኩት እና ይያዙት።"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"አንድ አቃፊ እነሆ"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"አንድ እንደዚህ አይነት ለመፍጠር መተግበሪያውን ነክተው ይያዙት እና ወደ ሌላ ያንቀሳቅሱት።"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"እሺ"</string>
+    <string name="folder_opened" msgid="94695026776264709">"አቃፊ ተከፍቷል፣ <xliff:g id="WIDTH">%1$d</xliff:g> በ<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"አቃፊን ለመዝጋት ይንኩ"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"ዳግም የተሰየመውን ለማስቀመጥ ይንኩ"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"አቃፊ ተዘግቷል"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"አቃፊ <xliff:g id="NAME">%1$s</xliff:g> ተብሎ ዳግም ተሰይሟል"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ፍርግሞች"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"የግድግዳ ወረቀቶች"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"ቅንብሮች"</string>
+</resources>
diff --git a/res/values-ar-land/strings.xml b/res/values-ar-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ar-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..73ee690
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"الرئيسية"</string>
+    <string name="uid_name" msgid="7820867637514617527">"تطبيقات Android الأساسية"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"تعيين خلفية"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"تم تحديد %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"تم تحديد %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"تم تحديد %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"الخلفية %1$d من %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"تم تحديد <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"حذف"</string>
+    <string name="pick_image" msgid="1272073934062909527">"اختيار صورة"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"الخلفيات"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"اقتصاص الخلفية"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"الأدوات"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"الأدوات"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"عرض الذاكرة"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"المس مع الاستمرار لاختيار إحدى الأدوات."</string>
+    <string name="market" msgid="2619650989819296998">"تسوق"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"تعذر إسقاط العنصر على هذه الشاشة الرئيسية."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"اختيار أداة لإنشائها"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"اسم المجلد"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"إعادة تسمية المجلد"</string>
+    <string name="rename_action" msgid="5559600076028658757">"موافق"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"إلغاء"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"إضافة إلى الشاشة الرئيسية"</string>
+    <string name="group_applications" msgid="3797214114206693605">"التطبيقات"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"الاختصارات"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"الأدوات"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"ليس هناك مساحة أخرى في الشاشات الرئيسية."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"ليست هناك مساحة أخرى في منطقة الإرساء القابلة للتخصيص."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"حجم هذه الأداة كبير للغاية بحيث لا تتسع له منطقة الإرساء القابلة للتخصيص."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"تم إنشاء الاختصار \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"تمت إزالة الاختصار \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"الاختصار \"<xliff:g id="NAME">%s</xliff:g>\" موجود من قبل."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"اختيار اختصار"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"اختيار تطبيق"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"التطبيقات"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"الرئيسية"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"إزالة"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"إزالة"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"إزالة"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إزالة"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"معلومات عن التطبيق"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"بحث"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"البحث الصوتي"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"التطبيقات"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"إزالة"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"إزالة التحديث"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"إزالة التطبيق"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"تفاصيل التطبيق"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"تم تحديد تطبيق واحد"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"تم تحديد أداة واحدة"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"تم تحديد مجلد واحد"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"تم تحديد اختصار واحد"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"إزالة الاختصارات"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"للسماح للتطبيق بإزالة الاختصارات بدون تدخل المستخدم."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"قراءة إعدادات واختصارات الشاشة الرئيسية"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"للسماح للتطبيق بقراءة الإعدادات والاختصارات في الشاشة الرئيسية."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"كتابة إعدادات واختصارات الشاشة الرئيسية"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"للسماح للتطبيق بتغيير الإعدادات والاختصارات في الشاشة الرئيسية."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"حدثت مشكلة أثناء تحميل الأداة"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"هذا تطبيق نظام وتتعذر إزالته."</string>
+    <string name="dream_name" msgid="1530253749244328964">"قاذفة صواريخ"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"مجلد بدون اسم"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"الشاشة الرئيسية %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"الصفحة %1$d من %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"الشاشة الرئيسية %1$d من %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"صفحة التطبيقات %1$d من %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"صفحة الأدوات %1$d من %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"مرحبًا!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"تصرف على راحتك."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"إنشاء المزيد من الشاشات للتطبيقات والمجلدات"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"تنظيم مساحتك"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"المس مع الاستمرار الجزء الخلفي من صورة الشاشة لإدارة الخلفية والأدوات والإعدادات."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"اختيار بعض التطبيقات"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"لإضافة تطبيق إلى الشاشة الرئيسية، المسه مع الاستمرار."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"إليك المجلد"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"لإنشاء مجلد مثل هذا، المس أحد التطبيقات مع استمرار اللمس، ثم حركه فوق آخر."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"موافق"</string>
+    <string name="folder_opened" msgid="94695026776264709">"تم فتح المجلد، بمقاس <xliff:g id="WIDTH">%1$d</xliff:g> في <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"المس لإغلاق المجلد"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"المس لحفظ إعادة التسمية"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"تم إغلاق المجلد"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"تمت إعادة تسمية المجلد إلى <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"الأدوات"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"الخلفيات"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"الإعدادات"</string>
+</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..ddcf404
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,212 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for application_name (5181331383435256801) -->
+    <skip />
+    <!-- no translation found for home (7658288663002113681) -->
+    <skip />
+    <!-- no translation found for uid_name (7820867637514617527) -->
+    <skip />
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <!-- no translation found for wallpaper_instructions (563973358787555519) -->
+    <skip />
+    <!-- no translation found for number_of_items_selected:zero (7464587177007785408) -->
+    <!-- no translation found for number_of_items_selected:one (142482526010824029) -->
+    <!-- no translation found for number_of_items_selected:other (1418352074806573570) -->
+    <!-- no translation found for wallpaper_accessibility_name (1655953108132967972) -->
+    <skip />
+    <!-- no translation found for announce_selection (8338254712932127413) -->
+    <skip />
+    <!-- no translation found for wallpaper_delete (8095005658756613921) -->
+    <skip />
+    <!-- no translation found for pick_image (1272073934062909527) -->
+    <skip />
+    <!-- no translation found for pick_wallpaper (8179698221502010609) -->
+    <skip />
+    <!-- no translation found for crop_wallpaper (8334345984491368009) -->
+    <skip />
+    <!-- no translation found for activity_not_found (8071924732094499514) -->
+    <skip />
+    <!-- no translation found for widgets_tab_label (2921133187116603919) -->
+    <skip />
+    <!-- no translation found for widget_adder (3201040140710381657) -->
+    <skip />
+    <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
+    <skip />
+    <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
+    <skip />
+    <!-- no translation found for market (2619650989819296998) -->
+    <skip />
+    <!-- no translation found for widget_dims_format (2370757736025621599) -->
+    <skip />
+    <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
+    <skip />
+    <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
+    <skip />
+    <!-- no translation found for rename_folder_label (3727762225964550653) -->
+    <skip />
+    <!-- no translation found for rename_folder_title (3771389277707820891) -->
+    <skip />
+    <!-- no translation found for rename_action (5559600076028658757) -->
+    <skip />
+    <!-- no translation found for cancel_action (7009134900002915310) -->
+    <skip />
+    <!-- no translation found for menu_item_add_item (1264911265836810421) -->
+    <skip />
+    <!-- no translation found for group_applications (3797214114206693605) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (6012256992764410535) -->
+    <skip />
+    <!-- no translation found for group_widgets (1569030723286851002) -->
+    <skip />
+    <!-- no translation found for completely_out_of_space (6106288382070760318) -->
+    <skip />
+    <!-- no translation found for out_of_space (4691004494942118364) -->
+    <skip />
+    <!-- no translation found for hotseat_out_of_space (9139760413395605841) -->
+    <skip />
+    <!-- no translation found for invalid_hotseat_item (1211534262129849507) -->
+    <skip />
+    <!-- no translation found for shortcut_installed (1701742129426969556) -->
+    <skip />
+    <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
+    <skip />
+    <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
+    <skip />
+    <!-- no translation found for title_select_shortcut (6680642571148153868) -->
+    <skip />
+    <!-- no translation found for title_select_application (3280812711670683644) -->
+    <skip />
+    <!-- no translation found for all_apps_button_label (9110807029020582876) -->
+    <skip />
+    <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
+    <skip />
+    <!-- no translation found for delete_target_label (1822697352535677073) -->
+    <skip />
+    <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
+    <skip />
+    <!-- no translation found for info_target_label (8053346143994679532) -->
+    <skip />
+    <!-- no translation found for accessibility_search_button (1628520399424565142) -->
+    <skip />
+    <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
+    <skip />
+    <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
+    <skip />
+    <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
+    <skip />
+    <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
+    <skip />
+    <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
+    <skip />
+    <!-- no translation found for cab_app_selection_text (374688303047985416) -->
+    <skip />
+    <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
+    <skip />
+    <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
+    <skip />
+    <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
+    <skip />
+    <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
+    <skip />
+    <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
+    <skip />
+    <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
+    <skip />
+    <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
+    <skip />
+    <!-- no translation found for permlab_read_settings (1941457408239617576) -->
+    <skip />
+    <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
+    <skip />
+    <!-- no translation found for permlab_write_settings (3574213698004620587) -->
+    <skip />
+    <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
+    <skip />
+    <!-- no translation found for gadget_error_text (6081085226050792095) -->
+    <skip />
+    <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
+    <skip />
+    <!-- no translation found for dream_name (1530253749244328964) -->
+    <skip />
+    <!-- no translation found for folder_hint_text (6617836969016293992) -->
+    <skip />
+    <!-- no translation found for workspace_description_format (2950174241104043327) -->
+    <skip />
+    <!-- no translation found for default_scroll_format (7475544710230993317) -->
+    <skip />
+    <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+    <skip />
+    <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
+    <skip />
+    <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
+    <skip />
+    <!-- no translation found for first_run_cling_title (7257389003637362144) -->
+    <skip />
+    <!-- no translation found for first_run_cling_description (6447072552696253358) -->
+    <skip />
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
+    <skip />
+    <!-- no translation found for workspace_cling_title (5626202359865825661) -->
+    <skip />
+    <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
+    <skip />
+    <!-- no translation found for all_apps_cling_title (34929250753095858) -->
+    <skip />
+    <!-- no translation found for all_apps_cling_add_item (400866858451850784) -->
+    <skip />
+    <!-- no translation found for folder_cling_title (3894908818693254164) -->
+    <skip />
+    <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
+    <skip />
+    <!-- no translation found for cling_dismiss (8962359497601507581) -->
+    <skip />
+    <!-- no translation found for folder_opened (94695026776264709) -->
+    <skip />
+    <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
+    <skip />
+    <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
+    <skip />
+    <!-- no translation found for folder_closed (4100806530910930934) -->
+    <skip />
+    <!-- no translation found for folder_renamed (1794088362165669656) -->
+    <skip />
+    <!-- no translation found for folder_name_format (6629239338071103179) -->
+    <skip />
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <!-- no translation found for widget_button_text (2880537293434387943) -->
+    <skip />
+    <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
+    <skip />
+    <!-- no translation found for settings_button_text (8119458837558863227) -->
+    <skip />
+</resources>
diff --git a/res/values-bg-land/strings.xml b/res/values-bg-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-bg-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..2f53fa3
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Начало"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Основни приложения на Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Задаване на тапета"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Избрахте %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Избрахте %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Избрахте %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Тапет %1$d от %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Избрахте <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Изтриване"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Избиране на изображение"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Тапети"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Подрязване на тапета"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Приложението не е инсталирано."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Приспособления"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Приспособления"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Показване на паметта"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Докоснете и задръжте за избор на приспособление."</string>
+    <string name="market" msgid="2619650989819296998">"Пазаруване"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Не можа да се премести на този начален екран."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Избор на приспособл. за създаване"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Име на папката"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Преименуване на папка"</string>
+    <string name="rename_action" msgid="5559600076028658757">"ОK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Отказ"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Добавяне към началния екран"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Приложения"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Преки пътища"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Приспособления"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"На началните ви екрани няма повече място."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"В трамплина няма повече място."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Това приспособление е твърде голямо за трамплина."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Прекият път към <xliff:g id="NAME">%s</xliff:g> е създаден."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Прекият път към <xliff:g id="NAME">%s</xliff:g> бе премахнат."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Прекият път към <xliff:g id="NAME">%s</xliff:g> вече съществува."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Избор на пряк път"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Избор на приложение"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Начало"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Премахване"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Деинсталиране"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Премахване"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Деинсталиране"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Информация за приложението"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Търсене"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Гласово търсене"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Приложения"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Премахване"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Деинст. на актуализацията"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Деинсталиране на приложението"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Подробности за приложението"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Избрано е 1 приложение"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Избрано е 1 приспособление"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Избрана е 1 папка"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Избран е 1 пряк път"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталиране на преки пътища"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Разрешава на приложението да добавя преки пътища без намеса на потребителя."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"деинсталиране на преки пътища"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Разрешава на приложението да премахва преки пътища без намеса на потребителя."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"четене на настройките и преките пътища в Начало"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Разрешава на приложението да чете настройките и преките пътища в Начало."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"запис на настройките и преките пътища в Начало"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Разрешава на приложението да променя настройките и преките пътища в Начало."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Проблем при зареждане на приспособлението"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Това е системно приложение и не може да се деинсталира."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Ракетна площадка"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Папка без име"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Начален екран %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Страница %1$d от %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Начален екран %1$d от %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Страница с приложения %1$d от %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Страница с приспособления %1$d от %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Добре дошли!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Персонализиране и приспособяване."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Създаване на още екрани за приложения и папки"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Организиране на мястото ви"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Докоснете и задръжте фона, за да управлявате тапета, приспособленията и настройките."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Изберете някои приложения"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"За да добавите приложение към началния си екран, го докоснете и задръжте."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ето една папка"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"За да създадете подобна, докоснете и задръжте приложение, след което го преместете върху друго."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"ОK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Папката е отворена – <xliff:g id="WIDTH">%1$d</xliff:g> на <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Докоснете, за да затворите папката"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Докоснете, за да запазите новото име"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Папката бе затворена"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана на „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Приспособления"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Настройки"</string>
+</resources>
diff --git a/res/values-ca-land/strings.xml b/res/values-ca-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ca-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..3400482
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Inici"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Aplicacions principals d\'Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Estableix el fons de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Seleccionats: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Seleccionats: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Seleccionats: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fons de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"S\'ha seleccionat <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Suprimeix"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Selecciona una imatge"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fons de pantalla"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Retalla el fons de pantalla"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"L\'aplicació no s\'ha instal·lat."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostra la memòria"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premut un widget per triar-lo."</string>
+    <string name="market" msgid="2619650989819296998">"Compra"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"No s\'ha pogut deixar anar l\'element a Inici."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Tria el widget que vulguis crear"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nom de la carpeta"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Canvi de nom de la carpeta"</string>
+    <string name="rename_action" msgid="5559600076028658757">"D\'acord"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancel·la"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Afegir a la pantalla d\'inici"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicacions"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Dreceres"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"No queda espai a les pantalles d\'inici."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"No queda espai al hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Aquest widget és massa gran per al hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"S\'ha creat la drecera \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"S\'ha suprimit la drecera \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"La drecera \"<xliff:g id="NAME">%s</xliff:g>\" ja existeix."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Tria d\'una drecera"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Tria d\'una aplicació"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacions"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Inici"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Suprimeix"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstal·la"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Suprimeix"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstal·la"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informació de l\'aplicació"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Cerca"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Cerca per veu"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicacions"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Suprimeix"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstal·la l\'actualització"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstal·la l\'aplicació"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalls de l\'aplicació"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicació seleccionada"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seleccionat"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 carpeta seleccionada"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 drecera seleccionada"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instal·la dreceres"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstal·la dreceres"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permet que l\'aplicació suprimeixi dreceres sense la intervenció de l\'usuari."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"llegeix la configuració i les dreceres de la pantalla d\'inici"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permet que l\'aplicació llegeixi la configuració i les dreceres de la pantalla d\'inici."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"escriu la configuració i les dreceres de la pantalla d\'inici"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permet que l\'aplicació canviï la configuració i les dreceres de la pantalla d\'inici."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"S\'ha produït un problema en carregar el widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Aquesta aplicació és una aplicació del sistema i no es pot desinstal·lar."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sense nom"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla d\'inici %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Pàgina %1$d de %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla d\'inici %1$d de %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pàgina d\'aplicacions %1$d de %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pàgina de widgets %1$d de %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Hola!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Personalitza la pantalla d\'inici"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea més pantalles per a aplicacions i carpetes"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organitza el teu espai"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Toca i mantén premut el fons per gestionar el fons de pantalla, els widgets i la configuració."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Tria unes quantes aplicacions"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Per afegir una aplicació a la pantalla d\'inici, mantén-la premuda."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Aquí hi ha una carpeta"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Per crear-ne una com aquesta, mantén premuda una aplicació i, a continuació, mou-la sobre una altra."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"D\'acord"</string>
+    <string name="folder_opened" msgid="94695026776264709">"S\'ha obert la carpeta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca per tancar la carpeta"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca per desar el canvi de nom"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Carpeta tancada"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"S\'ha canviat el nom de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fons de pantalla"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Configuració"</string>
+</resources>
diff --git a/res/values-cs-land/strings.xml b/res/values-cs-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-cs-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..1061e79
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Plocha"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Nastavit jako tapetu"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Vybráno: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Vybráno: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Vybráno: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Vybrána položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Smazat"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Vybrat obrázek"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Oříznutí tapety"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgety"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgety"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Zobrazit Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget vyberete dotykem a podržením."</string>
+    <string name="market" msgid="2619650989819296998">"Obchod"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Na tuto plochu položku nelze přesunout."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Vyberte widget k vytvoření"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Název složky"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Přejmenovat složku"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Zrušit"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Přidat na plochu"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikace"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Klávesové zkratky"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgety"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Na plochách již není místo."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"V sekci hotseat již není místo."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget je pro hotseat příliš velký."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> byl vytvořen."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> byl odebrán."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> již existuje."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Výběr zástupce"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Výběr aplikace"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikace"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Plocha"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstranit"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinstalovat"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Odstranit"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinstalovat"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informace o aplikaci"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Hledat"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hlasové vyhledávání"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikace"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstranit"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinstalovat aktualizaci"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinstalovat aplikaci"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikaci"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Vybrána 1 aplikace"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Vybrán 1 widget"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Vybrána 1 složka"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Vybrán 1 zástupce"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalace zástupce"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Umožňuje aplikaci přidat zástupce bez zásahu uživatele."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinstalovat zástupce"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Umožňuje aplikaci odstranit zástupce bez zásahu uživatele."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"čtení nastavení a odkazů plochy"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Umožňuje aplikaci číst nastavení a odkazy na ploše."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"zápis nastavení a odkazů plochy"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Umožňuje aplikaci změnit nastavení a odkazy na ploše."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problém s načtením widgetu"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Toto je systémová aplikace a nelze ji odinstalovat."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Složka bez názvu"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Plocha %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Strana %1$d z %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stránka aplikací %1$d z %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stránka widgetů %1$d z %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Vítejte!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Udělejte si pohodlí."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Vytvořte několik obrazovek pro aplikace a složky"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizace prostoru"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Chcete-li spravovat tapetu, widgety a nastavení, dotkněte se pozadí a přidržte je."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Vyberte nějaké aplikace"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Chcete-li na plochu přidat aplikaci, dotkněte se jí a přidržte ji."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Toto je složka"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Chcete-li vytvořit složku, přetáhněte aplikaci na jinou aplikaci."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Složka otevřena, rozměry <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotykem složku zavřete"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotykem uložíte změnu názvu"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Složka je uzavřena"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Složka přejmenována na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Složka: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgety"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Nastavení"</string>
+</resources>
diff --git a/res/values-da-land/strings.xml b/res/values-da-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-da-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..5db246e
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Startskærm"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Kerneapps i Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Angiv baggrund"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d er valgt"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d er valgt"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d er valgt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Baggrund %1$d af %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> blev valgt"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Slet"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Vælg billede"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Baggrunde"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Beskær baggrund"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Vis Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryk på en widget, og hold den nede for at vælge."</string>
+    <string name="market" msgid="2619650989819296998">"Køb i Google Play Butik"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Elementet kunne ikke trækkes til startskærmen."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Vælg den widget, du vil oprette"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mappenavn"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Omdøb mappe"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Annuller"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Føj til startskærm"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Genveje"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Der er ikke mere plads på dine startskærme."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Der er ikke mere plads i hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denne widget er for stor til hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" blev oprettet."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" blev fjernet."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" findes allerede."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Vælg genvej"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Vælg app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskærm"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Fjern"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Afinstaller"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Fjern"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Afinstaller"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Oplysninger om appen"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Søg"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Stemmesøgning"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Fjern"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Afinstaller opdatering"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Afinstaller appen"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Oplysninger om appen"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app er valgt"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget er valgt"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 mappe er valgt"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 genvej er valgt"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer genveje"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillader, at en app tilføjer genveje uden brugerens indgriben."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"afinstaller genveje"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Tillader, at appen fjerner genveje uden brugerens indgriben."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"læs indstillinger og genveje for startskærmen"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Tillader, at appen læser indstillingerne og genvejene på startskærmen."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"skriv indstillinger og genveje for startskærmen"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Tillader, at appen ændrer indstillingerne og genvejene på startskærmen."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Der er problemer med indlæsning af widgetten"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dette er en systemapp, som ikke kan afinstalleres."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Unavngiven mappe"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Startskærm %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d ud af %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskærm %1$d ud af %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-side %1$d ud af %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets-side %1$d ud af %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Velkommen"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Føl dig hjemme."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Opret flere skærme til apps og mapper"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser din arbejdsplads"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Tryk på en baggrund, og hold fingeren nede for at administrere baggrunde, widgets og indstillinger."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Vælge nogle apps"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Tryk på en app, og hold fingeren nede for at føje appen til startskærmen."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Her kan du se en mappe"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Du kan oprette en mappe magen til denne ved at trykke på en app og holde fingeren nede, mens du flytter appen til en anden mappe."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mappen er åben, <xliff:g id="WIDTH">%1$d</xliff:g> gange <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Tryk for at lukke mappen"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tryk for at gemme det nye navn"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mappen er lukket"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mappen er omdøbt til <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Baggrunde"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Indstillinger"</string>
+</resources>
diff --git a/res/values-de-land/strings.xml b/res/values-de-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-de-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..7a5e7e6
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Startseite"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Hintergrund auswählen"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d ausgewählt"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d ausgewählt"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d ausgewählt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Hintergrund %1$d von %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> ausgewählt"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Entfernen"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Bild auswählen"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Hintergründe"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Hintergrund zuschneiden"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Speicher anzeigen"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Zum Hinzufügen Widget berühren und halten"</string>
+    <string name="market" msgid="2619650989819296998">"Einkaufen"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Element wurde nicht auf diesem Startbildschirm abgelegt."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Widget zum Erstellen auswählen"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Ordnername"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Ordner umbenennen"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Abbrechen"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Zum Startbildschirm hinzufügen"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Verknüpfungen"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Auf Ihren Startbildschirmen ist kein Platz mehr vorhanden."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Kein Platz mehr auf der App-Leiste"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Dieses Widget ist zu groß für die App-Leiste."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" wurde erstellt."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" wurde entfernt."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" ist bereits vorhanden."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Verknüpfung auswählen"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"App auswählen"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startseite"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Entfernen"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deinstallieren"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Entfernen"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deinstallieren"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"App-Info"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Suchen"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Sprachsuche"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Entfernen"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Update deinstallieren"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"App deinstallieren"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"App-Details"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 App ausgewählt"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 Widget ausgewählt"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 Ordner ausgewählt"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 Verknüpfung ausgewählt"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"Verknüpfungen installieren"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"Verknüpfungen deinstallieren"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ermöglicht einer App das Entfernen von Verknüpfungen ohne Eingreifen des Nutzers"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"Einstellungen und Verknüpfungen auf dem Startbildschirm lesen"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu lesen"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"Einstellungen und Verknüpfungen für den Startbildschirm schreiben"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu ändern"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem beim Laden des Widgets"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dies ist eine Systemanwendung, die nicht deinstalliert werden kann."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Unbenannter Ordner"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Startbildschirm %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Seite %1$d von %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Startbildschirm %1$d von %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-Seite %1$d von %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets-Seite %1$d von %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Willkommen!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Gerät personalisieren"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Weitere Bildschirme für Apps und Ordner erstellen"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Arbeitsbereich organisieren"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Hintergrund berühren und halten, um Hintergrund, Widgets und Einstellungen zu verwalten"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Apps auswählen"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Berühren und halten Sie eine App, um sie zum Startbildschirm hinzuzufügen."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Hier ist ein Ordner"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Um einen Ordner zu erstellen, berühren und halten Sie eine App und verschieben Sie sie auf eine andere."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Ordner geöffnet, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Ordner durch Berühren schließen"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Umbenennung durch Berühren speichern"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Ordner geschlossen"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Ordner umbenannt in <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Hintergründe"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Einstellungen"</string>
+</resources>
diff --git a/res/values-el-land/strings.xml b/res/values-el-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-el-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..74322e0
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Αρχική σελίδα"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Βασικές εφαρμογές Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Ορισμός ταπετσαρίας"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d επιλεγμένα"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d επιλεγμένα"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d επιλεγμένα"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Ταπετσαρία %1$d από %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Επιλέχθηκε το <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Διαγραφή"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Επιλογή εικόνας"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Ταπετσαρίες"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Περικοπή ταπετσαρίας"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Η εφαρμογή δεν έχει εγκατασταθεί."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Γραφικά στοιχεία"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Γραφικά στοιχεία"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Εμφάνιση Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."</string>
+    <string name="market" msgid="2619650989819296998">"Αγορά"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Αδυναμία τοποθέτησης στοιχείου στην Αρχική οθόνη."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Επιλ. γραφ. στοιχείο για δημιουργία"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Όνομα φακέλου"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Μετονομασία φακέλου"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Ακύρωση"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Προσθήκη στην αρχική οθόνη"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Εφαρμογές"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Συντομεύσεις"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Γραφικά στοιχεία"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Δεν υπάρχει άλλος χώρος στις Αρχικές οθόνες σας."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Δεν υπάρχει άλλος χώρος στη γραμμή γρήγορης πρόσβασης."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Αυτό το γραφικό στοιχείο είναι πολύ μεγάλο για τη γραμμή γρήγορης πρόσβασης."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Δημιουργήθηκε η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\" καταργήθηκε."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\" υπάρχει ήδη."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Επιλογή συντόμευσης"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Επιλογή εφαρμογής"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Εφαρμογές"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Αρχική σελίδα"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Κατάργηση"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Κατάργηση εγκατάστασης"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Κατάργηση"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Κατάργηση εγκατάστασης"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Πληροφορίες εφαρμογής"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Αναζήτηση"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Φωνητική αναζήτηση"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Εφαρμογές"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Κατάργηση"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Κατάργηση εγκατάστασης ενημέρωσης"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Κατάργηση εγκατάστασης εφαρμογής"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Λεπτομέρειες εφαρμογής"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Επιλέχτηκε 1 εφαρμογή"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Επιλέχτηκε 1 γραφικό στοιχείο"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Επιλέχτηκε 1 φάκελος"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Επιλέχτηκε 1 συντόμευση"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"εγκατάσταση συντομεύσεων"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"κατάργηση εγκατάστασης συντομεύσεων"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Επιτρέπει στην εφαρμογή την κατάργηση συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Επιτρέπει στην εφαρμογή την ανάγνωση των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"εγγραφή ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Επιτρέπει στην εφαρμογή την αλλαγή των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Παρουσιάστηκε πρόβλημα στη φόρτωση του γραφικού στοιχείου"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Αυτή είναι μια εφαρμογή συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Φάκελος χωρίς όνομα"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Αρχική οθόνη %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Σελίδα %1$d από %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Αρχική οθόνη %1$d από %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Σελίδα εφαρμογών %1$d από %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Σελίδα γραφικών στοιχείων %1$d από %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Καλώς ορίσατε!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Νιώστε σαν στο σπίτι σας."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Δημιουργία περισσότερων οθονών για εφαρμογές και φακέλους"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Οργανώστε το χώρο σας"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Αγγίξτε παρατεταμένα το φόντο για να διαχειριστείτε την ταπετσαρία, τα γραφικά στοιχεία και τις ρυθμίσεις."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Επιλέξτε ορισμένες εφαρμογές"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Για να προσθέσετε μια εφαρμογή στην αρχική σας οθόνη, αγγίξτε την παρατεταμένα."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ορίστε ένας φάκελος"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Για να δημιουργήσετε έναν φάκελο σαν κι αυτόν, πατήστε παρατεταμένα μια εφαρμογή και στη συνέχεια, μετακινήστε τη πάνω σε μια άλλη."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Άνοιγμα φακέλου, <xliff:g id="WIDTH">%1$d</xliff:g> επί <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Αγγίξτε για να κλείσετε τον φάκελο"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Αγγίξτε για να αποθηκεύσετε το νέο όνομα"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Ο φάκελος έκλεισε"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Ο φάκελος μετονομάστηκε σε <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Γραφικά στοιχεία"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Ταπετσαρίες"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Ρυθμίσεις"</string>
+</resources>
diff --git a/res/values-en-rGB-land/strings.xml b/res/values-en-rGB-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-en-rGB-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..316585e
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Home"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Set wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selected"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selected"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selected"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Delete"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pick image"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpapers"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Crop wallpaper"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Show Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
+    <string name="market" msgid="2619650989819296998">"Shop"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Couldn\'t drop item on this Home screen."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Choose widget to create"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Folder name"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Rename folder"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancel"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Add to Home screen"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Shortcuts"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"No more room on your Home screens."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"No more room on the hot seat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"This widget is too large for the hot seat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" created."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" was removed."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" already exists."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Choose shortcut"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Choose app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remove"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Uninstall"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Remove"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Uninstall"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"App info"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Search"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Voice Search"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Remove"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Uninstall update"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uninstall app"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"App details"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selected"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selected"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder selected"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut selected"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"uninstall shortcuts"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Allows the app to remove shortcuts without user intervention."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps page %1$d of %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets page %1$d of %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Welcome!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Make yourself at home."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Create more screens for apps and folders"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organise your space"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Touch &amp; hold background to manage wallpaper, widgets and settings."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Choose some apps"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"To add an app to your Home screen, touch &amp; hold it."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Here\'s a folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"To create one like this, touch &amp; hold an app, then move it over another."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Touch to close folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Touch to save rename"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Settings"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..316585e
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Home"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Set wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selected"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selected"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selected"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Delete"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pick image"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpapers"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Crop wallpaper"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Show Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
+    <string name="market" msgid="2619650989819296998">"Shop"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Couldn\'t drop item on this Home screen."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Choose widget to create"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Folder name"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Rename folder"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancel"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Add to Home screen"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Shortcuts"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"No more room on your Home screens."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"No more room on the hot seat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"This widget is too large for the hot seat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" created."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" was removed."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" already exists."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Choose shortcut"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Choose app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remove"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Uninstall"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Remove"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Uninstall"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"App info"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Search"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Voice Search"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Remove"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Uninstall update"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uninstall app"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"App details"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selected"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selected"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder selected"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut selected"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"uninstall shortcuts"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Allows the app to remove shortcuts without user intervention."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps page %1$d of %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets page %1$d of %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Welcome!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Make yourself at home."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Create more screens for apps and folders"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organise your space"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Touch &amp; hold background to manage wallpaper, widgets and settings."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Choose some apps"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"To add an app to your Home screen, touch &amp; hold it."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Here\'s a folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"To create one like this, touch &amp; hold an app, then move it over another."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Touch to close folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Touch to save rename"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Settings"</string>
+</resources>
diff --git a/res/values-es-land/strings.xml b/res/values-es-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-es-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-es-rUS-land/strings.xml b/res/values-es-rUS-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-es-rUS-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..c9a44a9
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Pantalla principal"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Aplicaciones básicas de Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Establecer como fondo de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d seleccionados"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d seleccionado"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d seleccionados"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fondo de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Elegir imagen"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fondos de pantalla"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar fondo de pantalla"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memoria"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén presionado el widget que desees elegir."</string>
+    <string name="market" msgid="2619650989819296998">"Comprar"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Error al soltar elemento en la pantalla principal"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Elegir los widgets para crear"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nombre de carpeta"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Cambiar nombre de carpeta"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Aceptar"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Agregar a la pantalla principal"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicaciones"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Accesos directos"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"No hay más espacio en tus pantallas principales."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"No hay más espacio en la barra de accesos directos."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget es demasiado grande para la barra de accesos directos."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Se creó el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Se eliminó el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"El acceso directo \"<xliff:g id="NAME">%s</xliff:g>\" ya existe."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Elegir acceso directo"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Elegir aplicación"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Pantalla principal"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminar"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Eliminar"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Información de la aplicación"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Buscar"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Búsqueda por voz"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicaciones"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminar"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar actualización"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicación"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalles de la aplicación"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Se seleccionó 1 aplicación."</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Se seleccionó 1 widget."</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Se seleccionó 1 carpeta."</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Se seleccionó 1 acceso directo."</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación agregue accesos directos sin que el usuario intervenga."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar accesos directos"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que la aplicación elimine accesos directos sin que el usuario intervenga."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"leer configuración y accesos directos de la pantalla principal"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que la aplicación lea la configuración y los accesos directos de la pantalla principal."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"escribir configuración y accesos directos de la pantalla principal"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que la aplicación cambie la configuración y los accesos directos de la pantalla principal."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema al cargar el widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Esta es una aplicación del sistema y no se puede desinstalar."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Lanzacohetes"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sin nombre"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla principal %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla principal %1$d de %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicaciones %1$d de %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"¡Bienvenido/a!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Siéntete como en casa."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea más pantallas para aplicaciones y carpetas."</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiza tu espacio"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Mantén presionado el fondo para administrar el fondo de pantalla, los widgets y la configuración."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Elige algunas aplicaciones"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para agregar una aplicación a tu pantalla principal, mantenla presionada."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Aquí tienes una carpeta"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para crear una carpeta como esta, mantén presionada una aplicación y luego muévela sobre otra."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Aceptar"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca para cerrar la carpeta."</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca para guardar el nuevo nombre."</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Carpeta cerrada"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"El nombre de la carpeta se cambió a <xliff:g id="NAME">%1$s</xliff:g>."</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Configuración"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..193e096
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Home"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Aplicaciones básicas de Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Establecer fondo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Seleccionados: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Seleccionados: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Seleccionados: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fondo de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Seleccionar imagen"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fondos de pantalla"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar fondo de pantalla"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memoria"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén pulsado el widget que quieras seleccionar."</string>
+    <string name="market" msgid="2619650989819296998">"Tienda"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Error al arrastrar elemento a pantalla de inicio."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Selecciona widget a añadir"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nombre de carpeta"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Cambiar nombre de carpeta"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Aceptar"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Añadir a la pantalla de inicio"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicaciones"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Accesos directos"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"No queda espacio en las pantallas de inicio."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"No queda espacio en la barra de accesos directos."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget es demasiado grande para la barra de accesos directos."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Se ha creado el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Se ha eliminado el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"El acceso directo \"<xliff:g id="NAME">%s</xliff:g>\" ya existe."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Selecciona un acceso directo"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Selecciona una aplicación"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Inicio"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminar"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Eliminar"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Información de la aplicación"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Buscar"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Búsqueda por voz"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicaciones"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminar"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar actualización"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicación"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Información de la aplicación"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicación seleccionada"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seleccionado"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 carpeta seleccionada"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 acceso directo seleccionado"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación añada accesos directos sin intervención del usuario."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar accesos directos"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que la aplicación elimine accesos directos sin intervención del usuario."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"leer información de accesos directos y de ajustes de la pantalla de inicio"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que la aplicación consulte los ajustes y los accesos directos de la pantalla de inicio."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"escribir información de accesos directos y de ajustes de la pantalla de inicio"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que las aplicaciones cambien los ajustes y los accesos directos de la pantalla de inicio."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema al cargar el widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Esta aplicación es del sistema y no se puede desinstalar."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sin nombre"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla de inicio %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla de inicio %1$d de %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicaciones %1$d de %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Te damos la bienvenida"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Personaliza tu pantalla de inicio."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea más pantallas para aplicaciones y carpetas"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiza tu espacio"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Mantén pulsado el fondo para gestionar el fondo de pantalla, los widgets y los ajustes."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Selecciona algunas aplicaciones"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para añadir una aplicación a tu pantalla de inicio, solo tienes que mantenerla pulsada."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Esto es una carpeta"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para crear una carpeta como esta, mantén pulsada una aplicación y muévela sobre otra."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Aceptar"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca para cerrar la carpeta"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca para cambiar el nuevo nombre"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Carpeta cerrada"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Se ha cambiado el nombre de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Ajustes"</string>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..f026b08
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Avaekraan"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Androidi tuumrakendused"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Määra taustapilt"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Valitud on %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Valitud on %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Valitud on %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d/%2$d taustapildist"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Valitud on <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Kustuta"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Vali kujutis"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Taustapildid"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Taustapildi kärpimine"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Vidinad"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Vidinad"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mälu kuvamine"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidina valimiseks vajutage ja hoidke seda all."</string>
+    <string name="market" msgid="2619650989819296998">"Pood"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Üksust ei saanud sellele avaekraanile tuua."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Valige loomiseks vidin"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Kausta nimi"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Kausta ümbernimetamine"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Tühista"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Avaekraanile lisamine"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Rakendused"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Otseteed"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Vidinad"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Teie avaekraanidel ei ole enam ruumi."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Sellel avaekraanil pole enam ruumi."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Kohandataval dokialal pole rohkem ruumi."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"See vidin on kohandatava dokiala jaoks liiga suur."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on loodud."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on eemaldatud."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on juba olemas."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Otsetee valimine"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Rakenduse valimine"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Rakendused"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Avaekraan"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eemalda"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalli"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Eemalda"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalli"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Rakenduse teave"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Otsing"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Häälotsing"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Rakendused"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Eemalda"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalli värskendus"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Rakenduse desinstallimine"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Rakenduse üksikasjad"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Valitud on 1 rakendus"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Valitud on 1 vidin"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Valitud on 1 kaust"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Valitud on 1 otsetee"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installi otseteed"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalli otseteed"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"loe avaekraani seadeid ja otseteid"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Võimaldab rakendusel lugeda avaekraanil seadeid ja otseteid."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"kirjuta avaekraani seaded ja otseteed"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Võimaldab rakendusel muuta avaekraanil seadeid ja otseteid."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Probleem vidina laadimisel"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"See on süsteemirakendus ja seda ei saa desinstallida."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Nimetu kaust"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Avaekraan %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Leht %1$d/%2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Avaekraan %1$d/%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Rakenduste leht %1$d/%2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Vidinate leht %1$d/%2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Tere tulemast!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Tundke end nagu kodus."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Looge rakenduste ja kaustade jaoks rohkem ekraanikuvasid"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Korraldage oma ruumi"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Taustapildi, vidinate ja seadete haldamiseks puudutage tausta ning hoidke seda all."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Valige mõned rakendused"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Rakenduse lisamiseks avaekraanile vajutage ja hoidke seda all."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Siin on kaust"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Sarnase loomiseks vajutage ja hoidke rakendust all, seejärel viige see teise peale."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Puudutage kausta sulgemiseks"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Puudutage uue nime salvestamiseks"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Kaust on suletud"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Vidinad"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustapildid"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Seaded"</string>
+</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
new file mode 100644
index 0000000..acd0727
--- /dev/null
+++ b/res/values-et/strings.xml
@@ -0,0 +1,115 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="8424725141379931883">"Käivitaja"</string>
+    <string name="home" msgid="5921706419368316758">"Kodu"</string>
+    <string name="uid_name" msgid="3371120195364560632">"Androidi tuumrakendused"</string>
+    <string name="folder_name" msgid="8551881338202938211"></string>
+    <string name="chooser_wallpaper" msgid="6063168087625352235">"Taustapildi valimiskoht:"</string>
+    <string name="wallpaper_instructions" msgid="4215640646180727542">"Määra taustapilt"</string>
+    <string name="pick_wallpaper" msgid="5630222540525626723">"Taustapildid"</string>
+    <string name="activity_not_found" msgid="217823393239365967">"Rakendus pole installitud."</string>
+    <string name="widgets_tab_label" msgid="9145860100000983599">"Vidinad"</string>
+    <string name="long_press_widget_to_add" msgid="7395697462851217506">"Vidina valimiseks puudutage seda pikalt."</string>
+    <string name="market" msgid="2652226429823445833">"Pood"</string>
+    <string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="2285187188524172774">"Üksust ei saa sellele avaekraanile tuua."</string>
+    <string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Valige loomiseks vidin"</string>
+    <string name="rename_folder_label" msgid="5646236631298452787">"Kausta nimi"</string>
+    <string name="rename_folder_title" msgid="4544573104191526550">"Nimeta kaust ümber"</string>
+    <string name="rename_action" msgid="6016003384693240896">"OK"</string>
+    <string name="cancel_action" msgid="3811860427489435048">"Tühista"</string>
+    <string name="menu_item_add_item" msgid="6233177331075781114">"Lisa avamenüüsse"</string>
+    <string name="group_applications" msgid="2103752818818161976">"Rakendused"</string>
+    <string name="group_shortcuts" msgid="9133529424900391877">"Otseteed"</string>
+    <string name="group_widgets" msgid="6704978494073105844">"Vidinad"</string>
+    <string name="group_wallpapers" msgid="1568191644272224858">"Taustapildid"</string>
+    <string name="completely_out_of_space" msgid="1759078539443491182">"Teie avakuvadel ei ole enam ruumi."</string>
+    <string name="out_of_space" msgid="8365249326091984698">"Sellel avalehel pole enam ruumi."</string>
+    <string name="hotseat_out_of_space" msgid="6304886797358479361">"Kohandataval dokialal pole rohkem ruumi."</string>
+    <string name="invalid_hotseat_item" msgid="6545340627805449250">"See vidin on tööpunkti jaoks liiga suur."</string>
+    <string name="shortcut_installed" msgid="7071557296331322355">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” loodud."</string>
+    <string name="shortcut_uninstalled" msgid="2129499669449749995">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” eemaldatud."</string>
+    <string name="shortcut_duplicate" msgid="4757756326465060694">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on juba olemas."</string>
+    <string name="title_select_shortcut" msgid="1873670208166882222">"Otsetee valimine"</string>
+    <string name="title_select_application" msgid="1793455815754848652">"Rakenduse valimine"</string>
+    <string name="all_apps_button_label" msgid="2578400570124163469">"Rakendused"</string>
+    <string name="all_apps_home_button_label" msgid="1022222300329398558">"Kodu"</string>
+    <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Eemalda"</string>
+    <string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Desinstalli"</string>
+    <string name="delete_target_label" msgid="665300185123139530">"Eemalda"</string>
+    <string name="delete_target_uninstall_label" msgid="748894921183769150">"Desinstalli"</string>
+    <string name="info_target_label" msgid="4019495079517426980">"Rakenduse teave"</string>
+    <string name="accessibility_search_button" msgid="816822994629942611">"Otsing"</string>
+    <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Häälotsing"</string>
+    <string name="accessibility_all_apps_button" msgid="8803738611398979849">"Rakendused"</string>
+    <string name="accessibility_delete_button" msgid="3628162007991023603">"Eemalda"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Desinstalli värskendus"</string>
+    <string name="menu_add" msgid="3065046628354640854">"Lisa"</string>
+    <string name="menu_manage_apps" msgid="2308685199463588895">"Rakenduste haldamine"</string>
+    <string name="menu_wallpaper" msgid="5837429080911269832">"Taustapilt"</string>
+    <string name="menu_search" msgid="4826514464423239041">"Otsing"</string>
+    <string name="menu_notifications" msgid="6424587053194766192">"Teadistused"</string>
+    <string name="menu_settings" msgid="3946232973327980394">"Süsteemiseaded"</string>
+    <string name="menu_help" msgid="4901160661634590633">"Abi"</string>
+    <string name="cab_menu_delete_app" msgid="4089398025537640349">"Rakenduse desinstallimine"</string>
+    <string name="cab_menu_app_info" msgid="914548323652698884">"Rakenduse üksikasjad"</string>
+    <string name="cab_app_selection_text" msgid="6378522164293415735">"Valitud on 1 rakendus"</string>
+    <string name="cab_widget_selection_text" msgid="962527270506951955">"1 vidin on valitud"</string>
+    <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 kaust on valitud"</string>
+    <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 otsetee on valitud"</string>
+    <string name="permlab_install_shortcut" msgid="1201690825493376489">"otseteede installimine"</string>
+    <string name="permdesc_install_shortcut" msgid="8634424803272077038">"Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."</string>
+    <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"otseteede desinstallimine"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."</string>
+    <string name="permlab_read_settings" msgid="3452408290738106747">"avalehe seadete ja otseteede lugemine"</string>
+    <string name="permdesc_read_settings" msgid="5788109303585403679">"Võimaldab rakendusel lugeda avalehe seadeid ja otseteid."</string>
+    <string name="permlab_write_settings" msgid="1360567537236705628">"avalehe seadete ja otseteede kirjutamine"</string>
+    <string name="permdesc_write_settings" msgid="8530105489115785531">"Võimaldab rakendusel muuta avalehel seadeid ja otseteid."</string>
+    <string name="gadget_error_text" msgid="8359351016167075858">"Probleem vidina laadimisel"</string>
+    <string name="uninstall_system_app_text" msgid="6429814133777046491">"See on süsteemirakendus ja seda ei saa desinstallida."</string>
+    <string name="dream_name" msgid="2847171357608437154">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="8633351560105748141">"Nimeta kaust"</string>
+    <string name="workspace_description_format" msgid="2968608205939373034">"Avakuva %1$d"</string>
+    <string name="default_scroll_format" msgid="4057140866420001240">"Leht %1$d/%2$d"</string>
+    <string name="workspace_scroll_format" msgid="1704767047951143301">"Avakuva %1$d/%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"Rakenduste leht %1$d/%2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Vidinate leht %1$d/%2$d"</string>
+    <string name="workspace_cling_title" msgid="738396473989890567">"Tunne end nagu kodus"</string>
+    <string name="workspace_cling_move_item" msgid="791013895761065070">"Võite panna oma lemmikrakendused siia."</string>
+    <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Kõikide oma rakenduste nägemiseks puudutage ringi."</string>
+    <string name="all_apps_cling_title" msgid="2559734712581447107">"Valige mõned rakendused"</string>
+    <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Rakenduse lisamiseks avakuvale puudutage seda pikalt."</string>
+    <string name="folder_cling_title" msgid="4308949882377840953">"Korraldage oma rakendused kaustadesse"</string>
+    <string name="folder_cling_move_item" msgid="270598675060435169">"Rakenduse liigutamiseks pange sõrm rakendusele ja hoidke seda."</string>
+    <string name="folder_cling_create_folder" msgid="8352867485656129478">"Avakuval uue kausta tegemiseks virnastage üks rakendus teisele."</string>
+    <string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
+    <string name="folder_opened" msgid="1262064100943801533">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1335478160661137579">"Puudutage kausta sulgemiseks"</string>
+    <string name="folder_tap_to_rename" msgid="5201612989905472442">"Puudutage uue nime salvestamiseks"</string>
+    <string name="folder_closed" msgid="3130534551370511932">"Kaust suletud"</string>
+    <string name="folder_renamed" msgid="7951233572858053642">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="3051680259794759037">"<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
+    <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
+    <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
+    <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
+</resources>
diff --git a/res/values-fa-land/strings.xml b/res/values-fa-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fa-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..3a0d0b8
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"صفحه اصلی"</string>
+    <string name="uid_name" msgid="7820867637514617527">"برنامه‌های Android Core"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"تنظیم کاغذدیواری"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d انتخاب شد"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d انتخاب شد"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d انتخاب شد"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"کاغذ دیواری %1$d از %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> انتخاب شده"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"حذف"</string>
+    <string name="pick_image" msgid="1272073934062909527">"انتخاب تصویر"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"کاغذدیواری‌ها"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"برش کاغذ دیواری"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"برنامه نصب نشده است."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ابزارک‌ها"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ابزارک‌ها"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"نمایش Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"برای انتخاب ابزارک لمس کنید و نگه دارید."</string>
+    <string name="market" msgid="2619650989819296998">"فروشگاه"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"این مورد را نمی‌توان در این صفحه اصلی رها کرد."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"انتخاب ابزارکی که باید ایجاد شود"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"نام پوشه"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"تغییر نام پوشه"</string>
+    <string name="rename_action" msgid="5559600076028658757">"تأیید"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"لغو"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"افزودن به صفحه اصلی"</string>
+    <string name="group_applications" msgid="3797214114206693605">"برنامه‌ها"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"میان‌برها"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ابزارک‌ها"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"فضای بیشتری در صفحات نمایش اصلی شما موجود نیست."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"فضای بیشتری در جایگاه اتصال نیست."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"این ابزارک بیش از حد برای جایگاه اتصال بزرگ است."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"میان‌بر «<xliff:g id="NAME">%s</xliff:g>» ایجاد شد."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"میان‌بر «<xliff:g id="NAME">%s</xliff:g>» حذف شد."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"میان‌بر «<xliff:g id="NAME">%s</xliff:g>» در حال حاضر وجود دارد."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"انتخاب میان‌بر"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"انتخاب برنامه"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"برنامه‌ها"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"صفحه اصلی"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"حذف"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"حذف نصب"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"حذف"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"حذف نصب"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"اطلاعات برنامه"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"جستجو"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"جستجوی شفاهی"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"برنامه‌ها"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"حذف"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"حذف نصب به‌روزرسانی"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"حذف نصب برنامه"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"جزئیات برنامه"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"۱ برنامه انتخاب شد"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"۱ ابزارک انتخاب شد"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"۱ پوشه انتخاب شد"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"۱ میان‌بر انتخاب شد"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"نصب میان‌برها"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"به برنامه اجازه می‌دهد میان‌برها را بدون دخالت کاربر اضافه کند."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"حذف نصب میان‌برها"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"به برنامه اجازه می‌دهد میان‌برها را بدون دخالت کاربر حذف کند."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"خواندن تنظیمات و میان‌برهای صفحه اصلی"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"به برنامه اجازه می‌دهد تنظیمات و میان‌برها را در صفحه اصلی بخواند."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"نوشتن تنظیمات و میان‌برهای صفحه اصلی"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"به برنامه اجازه می‌دهد تنظیمات و میان‌برها را در صفحه اصلی تغییر دهد."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"مشکل در بارگیری ابزارک"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"این برنامه سیستمی است و حذف نصب نمی‌شود."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"پوشه بی‌نام"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"صفحه اصلی %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"صفحه %1$d از %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"صفحه اصلی %1$d از %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"صفحه برنامه‌ها %1$d از %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"صفحه ابزارک‌ها %1$d از %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"خوش آمدید!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"راحت باشید."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"صفحات بیشتری را برای برنامه‌ها و پوشه‌ها ایجاد کنید"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"فضای خود را سازماندهی کنید"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"برای مدیریت کاغذدیواری، ابزارک‌ها و تنظیمات، پس‌زمینه را لمس کرده و نگه‌دارید."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"چند برنامه انتخاب کنید"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"اگر می‌خواهید برنامه‌ای را به صفحه اصلی خود اضافه کنید، آن را لمس کرده، نگه‌دارید."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"اینجا یک پوشه است"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"برای ایجاد پوشه‌ای مثل این، یک برنامه را لمس کرده و نگه‌دارید، سپس آن را روی برنامه دیگر بیاندازید."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"تأیید"</string>
+    <string name="folder_opened" msgid="94695026776264709">"پوشه باز شده، <xliff:g id="WIDTH">%1$d</xliff:g> در <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"برای بستن پوشه لمس کنید"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"برای ذخیره تغییر نام لمس کنید"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"پوشه بسته شد"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ابزارک‌ها"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"کاغذدیواری‌ها"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"تنظیمات"</string>
+</resources>
diff --git a/res/values-fi-land/strings.xml b/res/values-fi-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fi-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..d327250
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Aloitusruutu"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Androidin ydinsovellukset"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Aseta taustakuva"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d valittu"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d valittu"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d valittu"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Taustakuva %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Valittu: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Poista"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Valitse kuva"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Taustakuvat"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Rajaa taustakuva"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgetit"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgetit"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Näytä muisti"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Valitse widget painamalla sitä pitkään."</string>
+    <string name="market" msgid="2619650989819296998">"Kauppa"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Kohteen lisääminen tähän aloitusruutuun epäonnistui."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Valitse luotava widget"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Kansion nimi"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Nimeä kansio uudelleen"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Peruuta"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Lisää aloitusruutuun"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Sovellukset"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Pikakuvakkeet"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgetit"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Aloitusruuduilla ei ole enää tilaa."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Hotseatissa ei ole enää tilaa."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Tämä widget on liian suuri tähän hotseat-paikkaan."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> luotiin."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> poistettiin."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> on jo olemassa."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Valitse pikakuvake"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Valitse sovellus"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Sovellukset"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Aloitusruutu"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Poista"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Poista"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Poista"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Poista"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Sovelluksen tiedot"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Haku"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Puhehaku"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Sovellukset"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Poista"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Poista päivitys"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Poista sovellus"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Sovelluksen tiedot"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 sovellus valittu"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget valittu"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 kansio valittu"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pikakuvake valittu"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"asenna pikakuvakkeita"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"poista pikakuvakkeita"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Antaa sovelluksen poistaa pikakuvakkeita ilman käyttäjän valintaa."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lue aloitusruudun asetuksia ja pikakuvakkeita"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Antaa sovelluksen lukea aloitusruudun asetukset ja pikakuvakkeet."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"kirjoita aloitusruudun asetuksia ja pikakuvakkeita"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Antaa sovelluksen muuttaa aloitusruudun asetuksia ja pikakuvakkeita."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Ongelma ladattaessa widgetiä"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Tämä on järjestelmäsovellus, eikä sitä voi poistaa."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Sinko"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Nimetön kansio"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Aloitusruutu %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Sivu %1$d / %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Aloitusruutu %1$d/%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-sivu %1$d / %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgetit-sivu %1$d / %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Tervetuloa!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Ole kuin kotonasi."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Luo lisää ruutuja sovelluksille ja kansioille"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Järjestä tilasi"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Hallitse taustakuvaa, widgetejä ja asetuksia koskettamalla taustaa pitkään."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Valitse joitakin sovelluksia"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Voit lisätä sovelluksen aloitusruutuun koskettamalla sitä pitkään."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Tässä on kansio"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Luo se seuraavasti: kosketa sovellusta pitkään ja siirrä se sitten toisen päälle."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Kansio avattu, koko <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Sulje kansio koskettamalla"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tallenna uudella nimellä koskettamalla"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Kansio on suljettu"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Kansion nimeksi vaihdettiin <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgetit"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustakuvat"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Asetukset"</string>
+</resources>
diff --git a/res/values-fr-land/strings.xml b/res/values-fr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fr-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..9d8dbeb
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Lanceur3"</string>
+    <string name="home" msgid="7658288663002113681">"Accueil"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Applications de base Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Définir le fond d\'écran"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d sélectionné"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d sélectionné"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d sélectionné(s)"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fond d\'écran %1$d sur %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Sélection : <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Supprimer"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Sélectionner une image"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fonds d\'écran"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Rogner le fond d\'écran"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afficher la mémoire"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Maintenez un doigt sur le widget pour l\'ajouter."</string>
+    <string name="market" msgid="2619650989819296998">"Magasiner"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Imposs. de déposer l\'élément sur l\'écran d\'accueil"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Sélectionnez le widget à créer"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nom du dossier"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Renommer le dossier"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Annuler"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Ajouter à l\'écran d\'accueil"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Applications"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Raccourcis"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Vous n\'avez plus de place sur la barre d\'accès rapide."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ce widget est trop volumineux pour la barre d\'accès rapide."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » a été créé."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » a été supprimé."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » existe déjà."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Sélectionner un raccourci"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Sélectionner une application"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Accueil"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Supprimer"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Désinstaller"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Supprimer"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Désinstaller"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Détails de l\'application"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Rechercher"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Recherche vocale"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Applications"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Supprimer"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Désinstaller la mise à jour"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Désinstaller l\'application"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Détails de l\'application"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 application sélectionnée"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget sélectionné"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dossier sélectionné"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 raccourci sélectionné"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"désinstaller des raccourcis"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permet à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"enregistrer les paramètres de la page d\'accueil et des raccourcis"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problème lors du chargement du widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Lance-missile"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Dossier sans nom"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Écran d\'accueil %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Page des applications : %1$d sur %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Page des widgets : %1$d sur %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Bienvenue!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Faites comme chez vous."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Créer plus d\'écrans pour les applications et les dossiers"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser son espace personnel"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Maintenez votre doigt sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Sélectionner des applications"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Pour ajouter une application à votre écran d\'accueil, maintenez votre doigt dessus."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Voici un dossier"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pour créer un dossier comme ça, maintenez votre doigt sur une application, puis déplacez-la sur une autre."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toucher pour fermer le dossier"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toucher pour enregistrer le nouveau nom"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Dossier fermé"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Paramètres"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..b1b0a79
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Accueil"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Applications de base Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Définir comme fond d\'écran"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d fond d\'écran sélectionné"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d fond d\'écran sélectionné"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d fonds d\'écran sélectionnés"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fond d\'écran %1$d sur %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> est sélectionné."</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Supprimer"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Sélectionner une image"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fonds d\'écran"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Rogner le fond d\'écran"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afficher la mémoire"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"App. de manière prolongée pour sélectionner widget."</string>
+    <string name="market" msgid="2619650989819296998">"Boutique"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Impossible de déposer élément sur écran d\'accueil."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Sélectionner le widget à créer"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nom du dossier"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Renommer le dossier"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Annuler"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Ajouter à l\'écran d\'accueil"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Applications"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Raccourcis"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Vous n\'avez plus de place sur la barre d\'accès rapide."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ce widget est trop volumineux pour la barre d\'accès rapide."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" a été créé."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" a été supprimé."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" existe déjà."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Sélectionner un raccourci"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Sélectionner une application"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Accueil"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Supprimer"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Désinstaller"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Supprimer"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Désinstaller"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informations sur l\'application"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Rechercher"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Recherche vocale"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Applications"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Supprimer"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Désinstaller la mise à jour"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Désinstaller l\'application"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Informations sur l\'application"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 application sélectionnée"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget sélectionné"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dossier sélectionné"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 raccourci sélectionné"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"désinstaller des raccourcis"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permettre à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permettre à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"modifier les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permettre à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problème lors du chargement du widget."</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Dossier sans nom"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Écran d\'accueil %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Page des applications %1$d sur %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Page des widgets %1$d sur %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Bienvenue !"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Familiarisez-vous avec l\'écran d\'accueil."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Créez des écrans personnalisés pour vos applis et dossiers"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organisez votre espace"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Appuyez de manière prolongée sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Sélectionner des applications"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Pour ajouter une application à votre écran d\'accueil, appuyez dessus de manière prolongée."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Voici un dossier"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pour en créer un, appuyez de manière prolongée sur une application, puis déplacez-la vers une autre."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Appuyez pour fermer le dossier."</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Appuyez pour enregistrer le nouveau nom."</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Dossier fermé"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Dossier \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Paramètres"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..eddd75c
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"होम"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android के मुख्य एप्लिकेशन"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"वॉलपेपर सेट करें"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d चयनित"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d चयनित"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d चयनित"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"वॉलपेपर %2$d में से %1$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"चयनित <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"हटाएं"</string>
+    <string name="pick_image" msgid="1272073934062909527">"चित्र चुनें"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"वॉलपेपर"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"वॉलपेपर काटें"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"एप्‍लिकेशन इंस्‍टॉल नहीं है."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"विजेट"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"विजेट"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"स्मृति दिखाएं"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्‍पर्श करके रखें."</string>
+    <string name="market" msgid="2619650989819296998">"खरीदारी करें"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"आइटम को इस होम स्‍क्रीन पर नहीं छोड़ा जा सका."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"बनाने के लिए विजेट चुनें"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"फ़ोल्‍डर का नाम"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"फ़ोल्‍डर का नाम बदलें"</string>
+    <string name="rename_action" msgid="5559600076028658757">"ठीक"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"रद्द करें"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"होम स्‍क्रीन में जोड़ें"</string>
+    <string name="group_applications" msgid="3797214114206693605">"एप्लिकेशन"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"शॉर्टकट"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"विजेट"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"आपकी होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"हॉटसीट पर स्थान शेष नहीं है."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"हॉटसीट के लि‍ए यह वि‍जेट बहुत बड़ा है."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" बनाया गया."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" निकाल दिया गया था."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" पहले से मौजूद है."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"शॉर्टकट चुनें"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"एप्‍लिकेशन चुनें"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"एप्लिकेशन"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"निकालें"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"अनइंस्टॉल करें"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"निकालें"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"अनइंस्टॉल करें"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"एप्लिकेशन की जानकारी"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"खोजें"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"बोलकर खोजें"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"एप्लिकेशन"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"निकालें"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"अपडेट अनइंस्‍टॉल करें"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"एप्लिकेशन अनइंस्‍टॉल करें"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"एप्लिकेशन का विवरण"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 एप्‍लिकेशन चयनित"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 विजेट चयनित"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 फ़ोल्‍डर चयनित"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 शॉर्टकट चयनित"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्‍टॉल करें"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"एप्लिकेशन को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"शॉर्टकट अनइंस्टॉल करें"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"एप्लिकेशन को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट निकालने देती है."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"होम सेटिंग और शॉर्टकट पढ़ें"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"एप्लिकेशन को होम में सेटिंग और शॉर्टकट पढ़ने देती है."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"होम सेटिंग और शॉर्टकट लिखें"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"एप्लिकेशन को होम में सेटिंग और शॉर्टकट बदलने देती है."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"विजेट लोड करने में समस्‍या"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"यह एक सिस्टम एप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता."</string>
+    <string name="dream_name" msgid="1530253749244328964">"रॉकेट लॉन्‍चर"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"अनामित फ़ोल्डर"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"होम स्‍क्रीन %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d में से %1$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"एप्लिकेशन पृष्ठ %2$d में से %1$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"विजेट पृष्ठ %2$d में से %1$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"स्वागत है!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"जैसा चाहें वैसा उपयोग करें."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"एप्लिकेशन और फ़ोल्डर के लिए और अधिक स्क्रीन बनाएं"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"अपने स्थान को व्यवस्थित करें"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"वॉलपेपर, विजेट और सेटिंग प्रबंधित करने के लिए पृष्ठभूमि को स्पर्श करके रखें."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"कुछ एप्लिकेशन चुनें"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"किसी एप्लिकेशन को अपनी होम स्‍क्रीन से जोड़ने के लिए, उसे स्‍पर्श करके रखें."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"यहां एक फ़ोल्डर है"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"इसके जैसा कोई एक बनाने के लिए, किसी एप्लिकेशन को स्पर्श करके रखें, फिर इसे किसी दूसरे पर ले जाएं."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"ठीक"</string>
+    <string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"फ़ोल्‍डर बंद करने के लिए स्‍पर्श करें"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"बदला गया नाम सहेजने के लिए स्पर्श करें"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"फ़ोल्डर बंद किया गया"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"फ़ोल्डर का नाम बदलकर <xliff:g id="NAME">%1$s</xliff:g> किया गया"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"विजेट"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"वॉलपेपर"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"सेटिंग"</string>
+</resources>
diff --git a/res/values-hr-land/strings.xml b/res/values-hr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-hr-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..783946d
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Pokretač3"</string>
+    <string name="home" msgid="7658288663002113681">"Početna"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Matične aplikacije za Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Postavi pozadinsku sliku"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Odabrano: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Odabrano: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Odabrano: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d. pozadinska slika od %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Odabrana je stavka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Izbriši"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Odaberi sliku"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Pozadinske slike"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Obreži pozadinsku sliku"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgeti"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgeti"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Prikaži mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i držite kako biste podigli widget."</string>
+    <string name="market" msgid="2619650989819296998">"Kupi"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Stavka nije ispuštena na ovaj početni zaslon."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Odabir widgeta za stvaranje"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Naziv mape"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Preimenovanje mape"</string>
+    <string name="rename_action" msgid="5559600076028658757">"U redu"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Odustani"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Dodavanje na početni zaslon"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikacije"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Prečaci"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgeti"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Na vašim početnim zaslonima više nema mjesta."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Na hotseatu više nema mjesta."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ovaj je widget prevelik za hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Izrađen je prečac za \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Uklonjen je prečac za \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Prečac za \"<xliff:g id="NAME">%s</xliff:g>\" već postoji."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Odabir prečaca"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Odabir aplikacije"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Početna"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ukloni"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deinstaliraj"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Ukloni"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deinstaliraj"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informacije o aplikaciji"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Pretraži"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Glasovno pretraživanje"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacije"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Ukloni"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Deinstalacija ažuriranja"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Deinstaliranje aplikacije"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Pojedinosti o aplikaciji"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Odabrana je 1 aplikacija"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Odabran je 1 widget"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Odabrana je 1 mapa"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Odabran je 1 prečac"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instaliranje prečaca"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"deinstaliranje prečaca"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Aplikaciji omogućuje uklanjanje prečaca bez intervencije korisnika."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"čitanje postavki početnog zaslona i prečaca"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Aplikaciji omogućuje čitanje postavki i prečaca na početnom zaslonu."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"pisanje postavki početnog zaslona i prečaca"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Aplikaciji omogućuje promjenu postavki i prečaca na početnom zaslonu."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem pri učitavanju widgeta"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ovo je aplikacija sustava i ne može se ukloniti."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Lansirna rampa"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Neimenovana mapa"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Početni zaslon %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Stranica %1$d od %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Početni zaslon %1$d od %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stranica aplikacija %1$d od %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stranica widgeta %1$d od %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Dobro došli!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Osjećajte se kao kod kuće."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Izradite više zaslona za aplikacije i mape"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizirajte svoj prostor"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Dodirnite i držite pozadinu da biste upravljali pozadinskom slikom, widgetima i postavkama."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Odaberite neke aplikacije"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Da biste dodali aplikaciju na početni zaslon, dodirnite je i zadržite."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Evo mape"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Da biste izradili ovakvu mapu, dodirnite i držite aplikaciju pa je pomaknite preko druge aplikacije."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"U redu"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mapa je otvorena, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Dodirnite da biste zatvorili mapu"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dodirnite da biste spremili preimenovanje"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mapa je zatvorena"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana u <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgeti"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadinske slike"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Postavke"</string>
+</resources>
diff --git a/res/values-hu-land/strings.xml b/res/values-hu-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-hu-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..8876652
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Főoldal"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Alap Android-alkalmazások"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Háttérkép beállítása"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d kiválasztva"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d kiválasztva"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d kiválasztva"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%2$d/%1$d. háttérkép"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> kiválasztva"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Törlés"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Kép választása"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Háttérképek"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Háttérkép körbevágása"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Modulok"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Modulok"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mem. megjelenítése"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Modul felvételéhez érintse meg, és tartsa lenyomva"</string>
+    <string name="market" msgid="2619650989819296998">"Vásárlás"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Nem lehet elemeket dobni erre a kezdőképernyőre."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"A létrehozáshoz válasszon modult"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mappa neve"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Mappa átnevezése"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Mégse"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Hozzáadás a kezdőképernyőhöz"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Alkalmazások"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Parancsikonok"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Modulok"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Nincs több hely a kezdőképernyőkön."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Nincs több hely az egyéni mezőben."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ez a modul túl nagy az egyéni mező számára."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon létrehozva."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon eltávolítva."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon már létezik."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Parancsikon választása"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Válasszon alkalmazást"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Alkalmazások"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Főoldal"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eltávolítás"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Eltávolítás"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Eltávolítás"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Eltávolítás"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Alkalmazásinformáció"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Keresés"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hangalapú keresés"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Alkalmazások"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Eltávolítás"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Eltávolítja a frissítést"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Alkalmazás eltávolítása"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Az alkalmazás adatai"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 alkalmazás kiválasztva"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 modul kiválasztva"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 mappa kiválasztva"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 parancsikon kiválasztva"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"parancsikonok telepítése"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"parancsikonok eltávolítása"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül távolítson el parancsikonokat."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"Főoldal beállításainak és parancsikonjainak beolvasása"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Lehetővé teszi az alkalmazás számára, hogy beolvassa a kezdőképernyő beállításait és parancsikonjait."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"Főoldal beállításainak és parancsikonjainak írása"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a kezdőképernyő beállításait és parancsikonjait."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Probléma történt a modul betöltésekor"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ez egy rendszeralkalmazás, és nem lehet eltávolítani."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Aknavető"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Névtelen mappa"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"%1$d. kezdőképernyő"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%2$d/%1$d. oldal"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d/%1$d. kezdőképernyő"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%2$d/%1$d. alkalmazásoldal"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%2$d/%1$d. moduloldal"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Üdvözöljük!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Varázsolja egyedivé készülékét."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Hozzon létre további képernyőket az alkalmazásoknak és mappáknak"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Munkaterület rendezése"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Érintse meg és tartsa lenyomva a hátteret a háttérkép, modulok és beállítások kezeléséhez."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Válasszon ki néhány alkalmazást"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ha egy alkalmazást szeretne elhelyezni a kezdőképernyőn, érintse meg, és tartsa lenyomva."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Itt egy mappa"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Mappa létrehozásához érintse meg és tartsa lenyomva az alkalmazást, majd húzza egy másik fölé."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mappa megnyitva – szélesség: <xliff:g id="WIDTH">%1$d</xliff:g>; magasság: <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Érintse meg a mappa bezárásához"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Érintse meg az átnevezés mentéséhez"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mappa lezárva"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"A mappa új neve: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Modulok"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Háttérképek"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Beállítások"</string>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..ab7388a
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Հիմնական"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Սահմանել պաստառը"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d ընտրված"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d ընտրված"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d ընտրված"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d պաստառ՝ %2$d-ից"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Ընտրված է <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Ջնջել"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Ընտրել պատկեր"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Պաստառներ"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Եզրատել պաստառը"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Ծրագիրը տեղադրված չէ:"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Վիջեթներ"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Վիջեթներ"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Ցուցադրել մեմը"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Հպեք և պահեք՝ վիջեթն ընտրելու համար:"</string>
+    <string name="market" msgid="2619650989819296998">"Խանութ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Հնարավոր չէ տեղադրել տարրն այս հիմնական էկրանին:"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Ստեղծելու համար ընտրեք վիջեթը"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Թղթապանակի անունը"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Վերանվանել թղթապանակը"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Լավ"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Չեղարկել"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Ավելացնել հիմնական էկրանին"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Ծրագրեր"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Դյուրանցումներ"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Վիջեթներ"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Այլևս տեղ չկա ձեր հիմնական էկրաններին:"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Թեժ նստատեղերում այլևս տեղ չկա:"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Այս վիջեթը չափազանց մեծ է թեժ նստատեղերի համար:"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումը ստեղծված է:"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումը հեռացվեց:"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումն արդեն գոյություն ունի:"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Ընտրել դյուրանցումը"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Ընտրել ծրագիրը"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Ծրագրեր"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Հիմնական"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Հեռացնել"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Ապատեղադրել"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Հեռացնել"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Ապատեղադրել"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Ծրագրի տեղեկություններ"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Որոնել"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ձայնային որոնում"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Ծրագրեր"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Հեռացնել"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Ապատեղադրել թարմացումը"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Ապատեղադրել ծրագիրը"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Ծրագրի մանրամասներ"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 ընտրված ծրագիր"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ընտրված վիջեթ"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ընտրված թղթապանակ"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 ընտրված դյուրանցում"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"տեղադրել դյուրանցումներ"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ապատեղադրել դյուրանցումները"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ծրագրին թույլ է տալիս հեռացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Ծրագրին թույլ է տալիս կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները:"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"ստեղծել հիմնաէջի կարգավորումներ ու դյուրանցումներ"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Ծրագրին թույլ է տալիս փոփոխել հիմնաէջի կարգավորումներն ու դյուրանցումները:"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Վիջեթի բեռնման խնդիր կա"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Սա համակարգային ծրագիր է և չի կարող ապատեղադրվել:"</string>
+    <string name="dream_name" msgid="1530253749244328964">"Հրթիռային թողարկիչ"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Անանուն թղթապանակ"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Հիմնական էկրան %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Էջ %1$d՝ %2$d-ից"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Հիմնական էկրան %1$d` %2$d-ից"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ծրագրերի էջերը՝ %1$d %2$d-ից"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Վիջեթների էջերը՝ %1$d %2$d-ից"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Բարի գալուստ:"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Զգացեք ձեզ ինչպես տանը:"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Ստեղծեք նոր էկրաններ ծրագրերի և թղթապանակների համար"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Կառավարեք ձեր տարածությունը"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Հպեք և պահեք հետնաշերտի վրա՝ պաստառները, վիջեթներն ու կարգավորումները կառավարելու համար:"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Ընտրեք ինչ-որ ծրագիր"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ձեր հիմնական էկրանին ծրագիր ավելացնելու համար հպեք և պահեք այն:"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ահա մի թղթապանակ"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Նման թղթապանակ ստեղծելու համար հպեք և պահեք որևէ ծրագրի վրա, ապա տեղաշարժեք այն մեկ ուրիշ ծրագրի վրա:"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Լավ"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Թղթապանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Հպեք՝ թղթապանակը փակելու համար"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Հպեք՝ վերանվանումը պահելու համար"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Թղթապանակը փակ է"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Թղթապանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Թղթապանակ՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Վիջեթներ"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Կարգավորումներ"</string>
+</resources>
diff --git a/res/values-in-land/strings.xml b/res/values-in-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-in-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..e1e93f7
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Layar Utama"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Aplikasi Inti Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Setel wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d dipilih"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d dipilih"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d dipilih"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d dari %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> terpilih"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Hapus"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pilih gambar"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpaper"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Pangkas wallpaper"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Tampilkan Memori"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
+    <string name="market" msgid="2619650989819296998">"Belanja"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Tidak dapat melepas item ke layar Utama ini."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pilih widget untuk membuat"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nama folder"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Ganti nama folder"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Oke"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Batal"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Tambahkan ke layar Utama"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikasi"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Pintasan"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Tidak ada ruang lagi di layar Utama Anda."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Tidak ada ruang lagi di hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget ini terlalu besar untuk hotseat tersebut."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah dibuat."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dihapus."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah ada."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Pilih pintasan"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Pilih aplikasi"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikasi"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Layar Utama"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Hapus"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Copot pemasangan"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Hapus"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Copot pemasangan"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Info aplikasi"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Telusuri"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Penelusuran Suara"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikasi"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Hapus"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Copot pemasangan pembaruan"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Copot aplikasi"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detail aplikasi"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplikasi dipilih"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget dipilih"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder dipilih"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pintasan dipilih"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"memasang pintasan"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"mencopot pemasangan pintasan"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Mengizinkan aplikasi menghapus pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"membaca setelan dan pintasan layar Utama"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Mengizinkan aplikasi membaca setelan dan pintasan di layar Utama."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"menulis setelan dan pintasan layar Utama"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Mengizinkan aplikasi mengubah setelan dan pintasan di layar Utama."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Masalah memuat widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ini adalah aplikasi sistem dan tidak dapat dicopot pemasangannya."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Folder Tanpa Nama"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Layar utama %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Laman %1$d dari %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Laman aplikasi %1$d dari %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Laman widget %1$d dari %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Selamat datang!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Serasa di rumah sendiri."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Buat lebih banyak layar untuk aplikasi dan folder"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Kelola ruang Anda"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Sentuh lama latar belakang untuk mengelola wallpaper, widget, dan setelan."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Pilih beberapa aplikasi"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Untuk menambah aplikasi ke layar Utama Anda, sentuh lama aplikasi tersebut."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ini adalah folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Untuk membuat seperti yang ini, sentuh lama aplikasi, lalu pindahkan ke atas aplikasi lain."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Oke"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Sentuh untuk menutup folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Sentuh untuk menyimpan ganti nama"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpaper"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Setelan"</string>
+</resources>
diff --git a/res/values-it-land/strings.xml b/res/values-it-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-it-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..d5ada8f
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Home page"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Applicazioni di base Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Imposta sfondo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selezionati"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selezionato"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selezionati"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Sfondo %1$d di %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Elemento selezionato: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Elimina"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Scegli immagine"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Sfondi"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Ritaglia sfondo"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostra Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tocca e tieni premuto per scegliere un widget."</string>
+    <string name="market" msgid="2619650989819296998">"Acquista"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Rilascio elemento in schermata Home non riuscito."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Scegli il widget da creare"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nome cartella"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Rinomina cartella"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Annulla"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Aggiungi a schermata Home"</string>
+    <string name="group_applications" msgid="3797214114206693605">"App"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Scorciatoie"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Spazio nelle schermate Home esaurito."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Spazio nell\'area hotseat esaurito."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Questo widget è troppo grande per l\'area hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" creata."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"La scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" è stata rimossa."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" già presente."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Scegli scorciatoia"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Scegli app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"App"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Home page"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Rimuovi"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Disinstalla"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Rimuovi"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Disinstalla"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informazioni app"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Cerca"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ricerca vocale"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"App"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Rimuovi"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Disinstalla aggiornamento"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Disinstalla app"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Dettagli sull\'app"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selezionata"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selezionato"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 cartella selezionata"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 scorciatoia selezionata"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"aggiunta di scorciatoie"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Consente a un\'app di aggiungere scorciatoie automaticamente."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"eliminazione di scorciatoie"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Consente all\'app di rimuovere scorciatoie automaticamente."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lettura di impostazioni e scorciatoie in Home"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Consente all\'app di leggere le impostazioni e le scorciatoie in Home."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"creazione di impostazioni e scorciatoie in Home"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Consente all\'app di modificare le impostazioni e le scorciatoie in Home."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Errore durante il caricamento del widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Questa è un\'app di sistema e non può essere disinstallata."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Lanciamissili"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Cartella senza nome"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Schermata Home %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d di %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Schermata Home %1$d di %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pagina di applicazioni %1$d di %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pagina di widget %1$d di %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Benvenuto!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Personalizza la schermata Home."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Creare più schermate per app e cartelle"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizza il tuo spazio"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Tocca e tieni premuto lo sfondo per gestire sfondi, widget e impostazioni."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Scegli alcune applicazioni"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Per aggiungere un\'app alla schermata Home, tocca e tieni premuto."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ecco una cartella"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Per crearne una simile, tocca un\'app e tieni premuto, dopodiché spostala sopra un\'altra."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Cartella aperta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Tocca per chiudere la cartella"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tocca per salvare nuovo nome"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Cartella chiusa"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Nome della cartella sostituito con <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Sfondi"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Impostazioni"</string>
+</resources>
diff --git a/res/values-iw-land/strings.xml b/res/values-iw-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-iw-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..603dbb5
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"דף הבית"</string>
+    <string name="uid_name" msgid="7820867637514617527">"אפליקציות הליבה של Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"הגדר טפט"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d נבחרו"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d נבחרו"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d נבחרו"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"טפט %1$d מתוך %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"בחרת <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"מחק"</string>
+    <string name="pick_image" msgid="1272073934062909527">"בחר תמונה"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"טפטים"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"חתוך את הטפט"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"האפליקציה לא מותקנת."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"רכיבי ווידג\'ט"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"רכיבי ווידג\'ט"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"הצג זכרון"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</string>
+    <string name="market" msgid="2619650989819296998">"קנה"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"לא ניתן היה לשחרר את הפריט במסך דף הבית הזה."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"בחר ווידג\'ט ליצירה"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"שם תיקיה"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"שנה את שם התיקיה"</string>
+    <string name="rename_action" msgid="5559600076028658757">"אישור"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"בטל"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"הוסף למסך דף הבית"</string>
+    <string name="group_applications" msgid="3797214114206693605">"אפליקציות"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"קיצורי דרך"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"רכיבי ווידג\'ט"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"אין יותר מקום במסכי דף הבית."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"אין יותר מקום בפס האפליקציות."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"הווידג\'ט הזה גדול מדי עבור פס האפליקציות."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' נוצר."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' הוסר."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' כבר קיים."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"בחר קיצור דרך"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"בחר אפליקציה"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"אפליקציות"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"דף הבית"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"הסר"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"הסר התקנה"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"הסר"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"הסר התקנה"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"פרטי אפליקציה"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"חפש"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"חיפוש קולי"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"אפליקציות"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"הסר"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"הסר את התקנת העדכון"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"הסר את התקנת האפליקציה"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"פרטי האפליקציה"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"נבחרה אפליקציה אחת"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"נבחר ווידג\'ט אחד"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"נבחרה תיקיה אחת"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"נבחר קיצור דרך אחד"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"התקן קיצורי דרך"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"הסר התקנה של קיצורי דרך"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"מאפשר לאפליקציה להסיר קיצורי דרך ללא התערבות המשתמש."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"קרא הגדרות וקיצורי דרך של דף הבית"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"מאפשר לאפליקציה לקרוא את ההגדרות וקיצורי הדרך בדף הבית."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"כתוב הגדרות וקיצורי דרך של דף הבית"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"מאפשר לאפליקציה לשנות את ההגדרות וקיצורי הדרך בדף הבית."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"בעיה בטעינת ווידג\'ט"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"תיקיה ללא שם"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"מסך דף הבית %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"דף %1$d מתוך %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"מסך דף הבית %1$d מתוך %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"דף אפליקציות %1$d מתוך %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"דף רכיבי ווידג\'ט ‏%1$d מתוך %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"ברוך הבא!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"להרגיש בבית."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"צור מסכים נוספים עבור אפליקציות ותיקיות"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"ארגן את אזור העבודה שלך"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"גע נגיעה רציפה ברקע כדי לנהל את הטפט, רכיבי הווידג\'ט וההגדרות."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"בחר כמה אפליקציות"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"כדי להוסיף אפליקציה למסך דף הבית, גע בה נגיעה רציפה."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"הנה תיקייה"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"כדי ליצור תיקייה כזו, גע נגיעה רציפה באפליקציה, ולאחר מכן גרור ושחרר אותו על-גבי אפליקציה אחרת."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"אישור"</string>
+    <string name="folder_opened" msgid="94695026776264709">"תיקיה פתוחה, <xliff:g id="WIDTH">%1$d</xliff:g> על <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"גע כדי לסגור את התיקיה"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"גע כדי לשמור את שינוי השם"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"התיקיה נסגרה"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"שם התיקיה שונה ל-<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"תיקיה: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"רכיבי ווידג\'ט"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"טפטים"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"הגדרות"</string>
+</resources>
diff --git a/res/values-ja-land/strings.xml b/res/values-ja-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ja-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..6e39c54
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"ホーム"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"壁紙を設定"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d件選択済み"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d件選択済み"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d件選択済み"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"壁紙: %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"選択: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"削除"</string>
+    <string name="pick_image" msgid="1272073934062909527">"画像を選択"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"壁紙"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"壁紙をトリミング"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"このアプリはインストールされていません。"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ウィジェット"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ウィジェット"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"メモリーを表示"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"ウィジェットを追加するには押し続けます。"</string>
+    <string name="market" msgid="2619650989819296998">"ショップ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"このホーム画面にアイテムをドロップできませんでした"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"作成するウィジェットの選択"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"フォルダ名"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"フォルダ名を変更"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"キャンセル"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"ホーム画面に追加"</string>
+    <string name="group_applications" msgid="3797214114206693605">"アプリ"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"ショートカット"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ウィジェット"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"ホーム画面に空きスペースがありません。"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"ホットシートに空きスペースがありません。"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"このウィジェットはホットシートには大きすぎます。"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」を作成しました。"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」を削除しました。"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」は既に存在します。"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"ショートカットを選択"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"アプリを選択"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"アプリ"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"ホーム"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"削除"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"アンインストール"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"削除"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"アンインストール"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"アプリ情報"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"検索"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"音声検索"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"アプリ"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"削除"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"更新をアンインストール"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"アプリをアンインストール"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"アプリの詳細"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1つのアプリが選択されています"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1つのウィジェットが選択されています"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1つのフォルダが選択されています"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1つのショートカットが選択されています"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ショートカットのインストール"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ユーザー操作なしでショートカットを追加することをアプリに許可します。"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ショートカットのアンインストール"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"ユーザー操作なしでショートカットを削除することをアプリに許可します。"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"ホームの設定とショートカットの読み取り"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"ホームの設定とショートカットの読み取りをアプリに許可します。"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"ホームの設定とショートカットの書き込み"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"ホームの設定とショートカットの変更をアプリに許可します。"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"ウィジェットを表示できません"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"このシステムアプリはアンインストールできません。"</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"名前のないフォルダ"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"ホーム画面: %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%1$d/%2$dページ"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"ホーム画面: %1$d/%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"アプリの%1$d/%2$dページ"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ウィジェットの%1$d/%2$dページ"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"ようこそ!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"ホームをカスタマイズします。"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"アプリとフォルダの画面をもっと作成します"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"スペースを整理"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"壁紙、ウィジェット、設定を管理するには、背景を押し続けます。"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"アプリの選択"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"アプリをホーム画面に追加するにはアプリを押し続けます。"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"これがフォルダです"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"これと同じフォルダを作成するには、アプリを押し続けてから別のアプリの上に移動します。"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"タップしてフォルダを閉じます"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"タップして名前の変更を保存します"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"フォルダは閉じています"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"フォルダの名前を「<xliff:g id="NAME">%1$s</xliff:g>」に変更しました"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ウィジェット"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"壁紙"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..a2e3374
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"მთავარი"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android-ის ბირთვის აპები"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"ფონის დაყენება"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"არჩეულია %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"არჩეულია %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"არჩეულია %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ფონი %1$d %2$d-დან"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"არჩეულია <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"წაშლა"</string>
+    <string name="pick_image" msgid="1272073934062909527">"სურათის ამორჩევა"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"ფონები"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"ფონის ჩამოჭრა"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"აპი არ არის დაყენებული."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ვიჯეტები"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ვიჯეტები"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mem-ის ჩვენება"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."</string>
+    <string name="market" msgid="2619650989819296998">"მაღაზია"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"ერთეულის მთავარ ეკრანზე ჩაგდება ვერ მოხერხდა."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"აირჩიეთ ვიჯეტი შესაქმნელად"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"საქაღალდის სახელი"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"საქაღალდის გადარქმევა"</string>
+    <string name="rename_action" msgid="5559600076028658757">"კარგი"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"გაუქმება"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"მთავარ ეკრანზე დამატება"</string>
+    <string name="group_applications" msgid="3797214114206693605">"აპები"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"მალსახმობები"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ვიჯეტები"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"მთავარ ეკრანებზე ადგილი აღარ არის."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"hotseat-ში მეტი ადგილი არ არის."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"ეს ვიჯეტი ძალიან დიდია hotseat-ისთვის."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"შეიქმნა მალსახმობი „<xliff:g id="NAME">%s</xliff:g>“."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"მასლახმობი „<xliff:g id="NAME">%s</xliff:g>“ წაშლილია."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"მალსახმობი „<xliff:g id="NAME">%s</xliff:g>“ უკვე არსებობს."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"აირჩიეთ მალსახმობი"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"აირჩიეთ აპი"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"აპები"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"მთავარი"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"წაშლა"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"დეინსტალაცია"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"წაშლა"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"დეინსტალაცია"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"აპის შესახებ"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"ძიება"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ხმოვანი ძიება"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"აპები"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"წაშლა"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"განახლების დეინსტალაცია"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"აპის დეინსტალაცია"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"აპის შესახებ"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"არჩეულია 1 აპი"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"არჩეულია 1 ვიჯეტი"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"არჩეულია 1 საქაღალდე"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"არჩეულია 1 მალსახმობი"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"მალსახმობების დაყენება"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"მალსახმობების წაშლა"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"აპისთვის მალსახმობების დამოუკიდებლად წაშლის უფლების მიცემა."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვის უფლების მიცემა."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"მთავარი ეკრანის პარამეტრებისა და მალსახმობების ჩაწერა"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების შეცვლის უფლების მიცემა."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"პრობლემა ვიჯეტის ჩატვირთვისას"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"ეს სისტემური აპია და მისი წაშლა შეუძლებელია."</string>
+    <string name="dream_name" msgid="1530253749244328964">"ფეიერვერკი"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"უსახელო საქაღალდე"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"მთავარი ეკრანი %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"გვერდი %1$d %2$d-დან"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"მთავარი ეკრანი %1$d, %2$d-დან"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"აპების გვერდი %1$d, %2$d-დან"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ვიჯეტების გვერდი %1$d, %2$d-დან"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"კეთილი იყოს თქვენი მობრძანება!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"იგრძენით თავი საკუთარ სახლში"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"აპებისა და საქაღალდეებისთვის კიდევ ერთი ეკრანის შექმნა"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"თქვენი სივრცის ორგანიზება"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"თუ გსურთ ფონების, ვიჯეტების და პარამეტრების მართვა, შეეხეთ და არ აუშვათ ფონს."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"აირჩიეთ რამდენიმე აპი"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"აპის მთავარ ეკრანზე დასამატებლად შეეხეთ მის ხატულას და არ აუშვათ."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"აი, საქაღალდე"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"ასეთის შესაქმნელად, შეეხეთ და დააყოვნეთ აპზე, ხოლო შემდეგ გადააჩოჩეთ შემდეგზე."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"კარგი"</string>
+    <string name="folder_opened" msgid="94695026776264709">"საქაღალდე გახსნილია, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"შეეხეთ საქაღალდის დასახურად"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"შეეხეთ ახალი სახელის შესანახად"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"საქაღალდე დაიხურა"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"საქაღალდეს შეეცვალა სახელი „<xliff:g id="NAME">%1$s</xliff:g>“-ად"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ვიჯეტები"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"ფონები"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"პარამეტრები"</string>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..977b2dd
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"ដើម"</string>
+    <string name="uid_name" msgid="7820867637514617527">"កម្មវិធី​​សំខាន់​ៗ​របស់ Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"កំណត់​ផ្ទាំង​រូបភាព"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"បាន​ជ្រើស %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"បាន​ជ្រើស %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"បាន​ជ្រើស %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ផ្ទាំង​រូបភាព %1$d នៃ %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"​បាន​ជ្រើស <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"លុប"</string>
+    <string name="pick_image" msgid="1272073934062909527">"ជ្រើស​យក​រូបភាព"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"ផ្ទាំង​រូបភាព"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"ច្រឹប​ផ្ទាំង​រូបភាព"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"មិន​បាន​ដំឡើង​កម្មវិធី។"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ធាតុ​ក្រាហ្វិក"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ធាតុ​ក្រាហ្វិក"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"បង្ហាញ​ Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"ប៉ះ &amp; សង្កត់ ដើម្បី​ជ្រើស​ធាតុ​ក្រាហ្វិក។"</string>
+    <string name="market" msgid="2619650989819296998">"ហាងទំនិញ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"មិន​អាច​ទម្លាក់​ធាតុ​លើ​អេក្រង់​ដើម​នេះ​ទេ"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ជ្រើស​ធាតុ​ក្រាហ្វិក ដើម្បី​​​បង្កើត"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"ឈ្មោះ​ថត"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"ប្ដូរ​ឈ្មោះ​ថត"</string>
+    <string name="rename_action" msgid="5559600076028658757">"យល់ព្រម"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"បោះបង់"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"បន្ថែម​ទៅ​អេក្រង់​ដើម"</string>
+    <string name="group_applications" msgid="3797214114206693605">"កម្មវិធី"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"ផ្លូវកាត់"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ធាតុ​ក្រាហ្វិក"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"គ្មាន​បន្ទប់​នៅ​លើ​អេក្រង់​ដើម​រស់​អ្នក​ទៀត​ទេ។"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"គ្មាន​បន្ទប់​នៅ​លើ​អេក្រង់​ដើម​នេះ​ទៀត​ទេ។"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"គ្មាន​បន្ទប់​នៅ​ក្នុង​មជ្ឈមណ្ឌល​ទៀត​ទេ។"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"ធាតុ​ក្រាហ្វិក​នេះ​ធំ​ពេក​សម្រាប់​មជ្ឈមណ្ឌល។"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"បាន​បង្កើត​ផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" ។"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"បាន​លុប​ផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" ។"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"មាន​ផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" រួច​ហើយ។"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"ជ្រើស​ផ្លូវកាត់"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"ជ្រើស​កម្មវិធី"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"កម្មវិធី"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"ដើម"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"លុប​ចេញ"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"លុប"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"លុប​ចេញ"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"លុប"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"ព័ត៌មាន​កម្មវិធី"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"ស្វែងរក"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ស្វែងរក​តាម​សំឡេង"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"កម្មវិធី"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"លុប​ចេញ"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"លុប​បច្ចុប្បន្នភាព"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"លុប​កម្មវិធី"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"ព័ត៌មាន​លម្អិត​កម្មវិធី"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"បាន​ជ្រើស​កម្មវិធី ១"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"បាន​ជ្រើស​ធាតុ ១"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"បាន​ជ្រើស​ថត ១"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"បាន​ជ្រើស​ផ្លូវកាត់ ១"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ដំឡើង​ផ្លូវកាត់"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​បន្ថែម​ផ្លូវកាត់​ ដោយ​មិន​ចាំបាច់​​អំពើ​ពី​អ្នក​ប្រើ។"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"លុប​ផ្លូវកាត់"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​លុប​ផ្លូវកាត់​ដោយ​មិន​ចាំបាច់​អំពើ​ពី​អ្នក​ប្រើ។"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"អាន​ការ​កំណត់​ និង​ផ្លូវកាត់​​អេក្រង់​ដើម"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​អាន​ការ​កំណត់ និង​ផ្លូវកាត់​ក្នុង​អេក្រង់​ដើម។"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"សរសេរ​ការ​កំណត់ ​និង​ផ្លូវកាត់​​លើ​អេក្រង់​ដើម"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​ប្ដូរ​ការ​កំណត់ និង​ផ្លូវ​កាត់​ក្នុង​អេក្រង់​ដើម។"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"បញ្ហា​ក្នុង​ការ​ផ្ទុក​ធាតុ​​ក្រាហ្វិក"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"នេះ​​​ជា​កម្មវិធី​ប្រព័ន្ធ មិន​អាច​លុប​បាន​ទេ។"</string>
+    <string name="dream_name" msgid="1530253749244328964">"កម្មវិធី​ចាប់ផ្ដើម​រ៉ូកែត"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"ថត​គ្មាន​ឈ្មោះ"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"អេក្រង់​ដើម %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"ទំព័រ %1$d នៃ %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"អេក្រង់​ដើម %1$d នៃ %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"ទំព័រ​កម្មវិធី %1$d នៃ %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ទំព័រ​ធាតុ​ក្រាហ្វិក ​%1$d នៃ %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"សូម​ស្វាគមន៍​!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"ធ្វើ​ដោយ​ខ្លួន​ឯង​នៅ​លើ​អេក្រង់​ដើម។"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"បង្កើត​អេក្រង់​ច្រើន​សម្រាប់​កម្មវិធី​ ​និង​ថតឯកសារ"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"រៀបចំ​ចន្លោះ​របស់​អ្នក"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"ប៉ះ &amp; សង្កត់​លើ​ផ្ទៃ​ខាង​ក្រោម ដើម្បី​គ្រប់គ្រង​ផ្ទាំង​រូបភាព, ធាតុ​ក្រាហ្វិក និង​ការ​កំណត់។"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"ជ្រើស​កម្មវិធី​មួយ​ចំនួន"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"ប៉ះ​ &amp; សង្កត់​វា ដើម្បី​បន្ថែម​កម្មវិធី​ទៅ​​​អេក្រង់​ដើម​របស់​អ្នក"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"នេះ​ជា​ថត"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"ដើម្បី​បង្កើត​មួយ​ដូច​នេះ ប៉ះ &amp; សង្កត់​​លើ​កម្មវិធី បន្ទាប់​មក​ផ្លាស់ទី​វា​ទៅ​លើ​ធាតុ​មួយ​ផ្សេង​ទៀត។"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"យល់ព្រម"</string>
+    <string name="folder_opened" msgid="94695026776264709">"បាន​បើក​ថត <xliff:g id="WIDTH">%1$d</xliff:g> ដោយ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"ប៉ះ ដើម្បី​បិទ​ថត"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"ប៉ះ ដើម្បី​រក្សាទុក​ការ​ប្ដូរ​ឈ្មោះ"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"បាន​បិទ​ថត"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"បាន​ប្ដូរ​ឈ្មោះ​ថត​ជា <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ធាតុ​ក្រាហ្វិក"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"ផ្ទាំង​រូបភាព"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"ការកំណត់"</string>
+</resources>
diff --git a/res/values-ko-land/strings.xml b/res/values-ko-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ko-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..31c2d69
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"홈"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android 핵심 앱"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"배경화면 설정"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d개 선택됨"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d개 선택됨"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d개 선택됨"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"배경화면 %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> 선택함"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"삭제"</string>
+    <string name="pick_image" msgid="1272073934062909527">"이미지 선택"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"배경화면"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"배경화면 잘라내기"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"앱이 설치되지 않았습니다."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"위젯"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"위젯"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"메모리 표시"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"위젯을 선택하려면 길게 터치하세요."</string>
+    <string name="market" msgid="2619650989819296998">"쇼핑하기"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"홈 화면에 항목을 놓을 수 없습니다."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"만들 위젯 선택"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"폴더 이름"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"폴더 이름 바꾸기"</string>
+    <string name="rename_action" msgid="5559600076028658757">"확인"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"취소"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"홈 화면에 추가"</string>
+    <string name="group_applications" msgid="3797214114206693605">"앱"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"바로가기"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"위젯"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"홈 화면에 더 이상 공간이 없습니다."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"즐겨찾는 앱 모음에 더 이상 빈 공간이 없습니다."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"이 위젯은 너무 커서 즐겨찾는 앱 모음에 들어갈 수 없습니다."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 생성되었습니다."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 삭제되었습니다."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 이미 있습니다."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"바로가기 선택"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"앱 선택"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"앱"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"홈"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"삭제"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"제거"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"삭제"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"제거"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"앱 정보"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"검색"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"음성 검색"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"앱"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"삭제"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"업데이트 제거"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"앱 제거"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"앱 세부정보"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"앱 1개 선택됨"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"위젯 1개 선택됨"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"폴더 1개 선택됨"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"바로가기 1개 선택됨"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"바로가기 설치"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"바로가기 제거"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"앱이 사용자의 작업 없이 바로가기를 삭제할 수 있도록 합니다."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"홈 설정 및 바로가기 읽기"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"앱이 홈에 있는 설정 및 바로가기를 읽을 수 있도록 합니다."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"홈 설정 및 바로가기 쓰기"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"앱이 홈에 있는 설정 및 바로가기를 변경할 수 있도록 합니다."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"위젯을 로드하는 중 문제가 발생했습니다."</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"시스템 앱은 제거할 수 없습니다."</string>
+    <string name="dream_name" msgid="1530253749244328964">"로켓 실행기"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"이름이 없는 폴더"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"홈 화면 %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"페이지 %1$d/%2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"홈 화면 %1$d/%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"앱 페이지 %1$d/%2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"위젯 페이지 %1$d/%2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"환영합니다!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"편리한 사용 환경을 만드세요."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"앱 및 폴더를 표시할 화면 더 만들기"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"공간 관리하기"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"배경화면, 위젯, 설정을 관리하려면 백그라운드를 길게 터치합니다."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"앱 선택하기"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"홈 화면에 앱을 추가하려면 길게 터치합니다."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"폴더"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"폴더를 만들려면 앱을 길게 터치한 다음 다른 앱 위에 올려 놓으세요."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"확인"</string>
+    <string name="folder_opened" msgid="94695026776264709">"폴더 열림(<xliff:g id="WIDTH">%1$d</xliff:g>X<xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"터치하여 폴더를 닫음"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"터치하여 바꾼 이름을 저장"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"폴더 닫음"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"폴더 이름 변경: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"폴더: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"위젯"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"배경화면"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"설정"</string>
+</resources>
diff --git a/res/values-land/config.xml b/res/values-land/config.xml
new file mode 100644
index 0000000..121bb0c
--- /dev/null
+++ b/res/values-land/config.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources>
+<!-- Workspace -->
+    <!-- Whether or not the drop targets drop down as opposed to fade in -->
+    <bool name="config_useDropTargetDownTransition">false</bool>
+    <!-- Whether or not to fade the side pages -->
+    <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+</resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
new file mode 100644
index 0000000..5961b19
--- /dev/null
+++ b/res/values-land/dimens.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">8dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">0dip</dimen>
+
+<!-- Workspace -->
+    <!-- We really want the page spacing to be the max of either the button bar
+     height or the qsb bar height -->
+    <dimen name="workspace_page_spacing">-1dp</dimen>
+
+<!-- AppsCustomize -->
+    <dimen name="apps_customize_tab_bar_height">42dp</dimen>
+    <integer name="apps_customize_widget_cell_count_x">3</integer>
+    <integer name="apps_customize_widget_cell_count_y">2</integer>
+    <integer name="apps_customize_cling_focused_x">2</integer>
+    <integer name="apps_customize_cling_focused_y">1</integer>
+</resources>
diff --git a/res/values-land/strings.xml b/res/values-land/strings.xml
new file mode 100644
index 0000000..ec4c7e7
--- /dev/null
+++ b/res/values-land/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Manifest configuration. -->
+    <skip />
+</resources>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000..ccb5fcb
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+
+<resources>
+<!-- Search Bar -->
+    <style name="QSBBar">
+    </style>
+    <style name="SearchDropTargetBar">
+    </style>
+    <style name="SearchButton">
+    </style>
+    <style name="DropTargetButtonContainer">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">0dp</item>
+    </style>
+    <style name="DropTargetButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingStart">@dimen/toolbar_button_horizontal_padding</item>
+        <item name="android:paddingEnd">@dimen/toolbar_button_horizontal_padding</item>
+        <item name="android:shadowColor">#DD000000</item>
+        <item name="android:shadowDx">0.0</item>
+        <item name="android:shadowDy">1.0</item>
+        <item name="android:shadowRadius">4.0</item>
+    </style>
+
+<!-- AppsCustomize -->
+    <style name="TabIndicator.AppsCustomize">
+        <item name="android:maxWidth">200dp</item>
+    </style>
+</resources>
+
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..e3a95dd
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"ໜ້າຫຼັກ"</string>
+    <string name="uid_name" msgid="7820867637514617527">"ແອັບພລິເຄຊັນຫຼັກຂອງ Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"ຕັ້ງເປັນພາບພື້ນຫຼັງ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+    <item quantity="one" msgid="142482526010824029">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+    <item quantity="other" msgid="1418352074806573570">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ຮູບພືື້ນຫຼັງ %1$d ໃນ %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"ເລືອກແລ້ວ <xliff:g id="LABEL">%1$s</xliff:g> ອັນ"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"ລຶບ"</string>
+    <string name="pick_image" msgid="1272073934062909527">"ເລືອກຮູບ"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"ພາບພື້ນຫຼັງ"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"ຕັດຮູບພື້ນຫຼັງ"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"ວິດເຈັດ"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"ວິດເຈັດ"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"ສະແດງຄວາມຈຳ"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."</string>
+    <string name="market" msgid="2619650989819296998">"ຮ້ານຄ້າ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"ບໍ່ສາມາດວາງລາຍການໃສ່ໜ້າຈໍຫຼັກນີ້ໄດ້"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ເລືອກວິດເຈັດເພື່ອສ້າງມັນ"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"ຊື່ໂຟນເດີ"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"ປ່ຽນຊື່ໂຟນເດີ"</string>
+    <string name="rename_action" msgid="5559600076028658757">"ຕົກລົງ"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"ຍົກ​ເລີກ"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"</string>
+    <string name="group_applications" msgid="3797214114206693605">"ແອັບຯ"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"ທາງລັດ"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"ວິດເຈັດ"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກຂອງທ່ານ."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"ບໍ່ມີຫ້ອງຫວ່າງໃນ hotseat ແລ້ວ."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"ວິດເຈັດ ມີຂະໜາດໃຫຍ່ເກີນໄປສຳລັບ hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ຖືກສ້າງແລ້ວ."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ຖືກລຶບແລ້ວ."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ມີຢູ່ແລ້ວ."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"ເລືອກທາງລັດ"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"ເລືອກແອັບຯ"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"ແອັບຯ"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"ໜ້າຫຼັກ"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"ລຶບ"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"ຖອນການຕິດຕັ້ງ"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"ລຶບ"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"ຖອນການຕິດຕັ້ງ"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"ຂໍ້ມູນແອັບຯ"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"ຊອກຫາ"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ຊອກຫາດ້ວຍສຽງ"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"ແອັບຯ"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"ລຶບ"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ຖອນອັບເດດ"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"ຖອນແອັບຯ"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"ລາຍລະອຽດແອັບຯ"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 ແອັບຯຖືກເລືອກ"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ວິດເຈັດຖືກເລືອກ"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ໂຟນເດີຖືກເລືອກ"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 ທາງລັດຖືກເລືອກ"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ຕິດຕັ້ງທາງລັດ"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ຖອນທາງລັດ"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ລຶບທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວອ່ານການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"ຂຽນການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ປ່ຽນການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"ມີບັນຫາໃນການໂຫລດວິດເຈັດ"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"ນີ້ແມ່ນແອັບຯຂອງລະບົບ ແລະບໍ່ສາມາດຖອນການຕິດຕັ້ງອອກໄດ້."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"ໂຟນເດີຍັງບໍ່ຖືກຕັ້ງຊື່"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"ໜ້າຈໍຫຼັກ %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"ໜ້າ %1$d ຈາກ %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"ແອັບຯໜ້າ %1$d ໃນ %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ວິດເຈັດໜ້າ %1$d ໃນ %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"ຍິນດີຕ້ອນຮັບ!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"ເຮັດໂຕໃຫ້ຄືຢູ່ໃນບ້ານ"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"ສ້າງຈໍເພີ່ມເຕີມສຳລັບແອັບຯ ແລະໂຟນເດີ"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"ຈັດການພື້ນທີ່ຂອງທ່ານ"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"ແຕະຄ້າງໄວ້ທີ່ພາບພື້ນຫຼັງເພື່ອຈັດການພາບພື້ນຫຼັງ, ວິດເຈັດແລະການຕັ້ງຄ່າ."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"ເລືອກແອັບຯ"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"ເພື່ອເພີ່ມແອັບຯໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ, ໃຫ້ແຕະຄ້າງໄວ້."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"ນີ້ແມ່ນໂຟນເດີ່"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"ເພື່ອສ້າງອັນໃໝ່ແບບນີ້, ແຕະຄ້າງໄວ້ທີ່ແອັບຯ ແລ້ວຍ້າຍມັນໄປຫາໂຕອື່ນ."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"ຕົກລົງ"</string>
+    <string name="folder_opened" msgid="94695026776264709">"ເປີດໂຟນເດີແລ້ວ, <xliff:g id="WIDTH">%1$d</xliff:g> ຄູນ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"ສຳພັດເພື່ອປິດໂຟນເດີ"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"ສຳພັດເພື່ອບັນທຶກການປ່ຽນຊື່"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"ປິດໂຟນເດີແລ້ວ"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"ປ່ຽນຊື່ໂຟນເດີເປັນ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"ວິດເຈັດ"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"ພາບພື້ນຫຼັງ"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"ການຕັ້ງຄ່າ"</string>
+</resources>
diff --git a/res/values-lt-land/strings.xml b/res/values-lt-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-lt-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..80d4412
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Pagrindinis"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Pagrindinės „Android“ programos"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Nustatyti ekrano foną"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Pasirinkta: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Pasirinkta: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Pasirinkta: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d iš %2$d ekrano fonų"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Pasirinkta: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Ištrinti"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pasirinkti vaizdą"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Ekrano fonai"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Ekrano fono apkirpimas"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Valdikliai"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Valdikliai"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Rodyti atmintinę"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Palieskite ir laikykite, kad pasirinkt. valdiklį."</string>
+    <string name="market" msgid="2619650989819296998">"Apsipirkti"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Nepavyko nuvilkti elemento į šį pagrindinį ekraną."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pasirinkite norimą kurti valdiklį"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Aplanko pavadinimas"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Pervardyti aplanką"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Gerai"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Atšaukti"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Pridėti prie pagrindinio ekrano"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Programos"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Spartieji klavišai"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Valdikliai"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Pagrindiniuose ekranuose vietos nebėra."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Įtvirtintojoje srityje nebėra vietos."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Šis valdiklis įtvirtintajai sričiai per didelis."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ sukurtas."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ pašalintas."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ jau yra."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Pasirinkite spartųjį klavišą"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Pasirinkite programą"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Programos"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Pagrindinis"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Pašalinti"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Pašalinti"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Pašalinti"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Pašalinti"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Programos informacija"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Ieškoti"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Paieška balsu"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programos"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Pašalinti"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Pašalinti naujinį"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Pašalinti programą"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Išsami programos informacija"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Pasirinkta 1 programa"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Pasirinktas 1 valdiklis"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Pasirinktas 1 aplankas"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Pasirinktas 1 spartusis klavišas"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"įdiegti sparčiuosius klavišus"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"pašalinti sparčiuosius klavišus"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Programai leidžiama pašalinti sparčiuosius klavišus be naudotojo įsikišimo."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Programai leidžiama skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"rašyti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Programai leidžiama keisti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema įkeliant valdiklį"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Tai sistemos programa ir jos negalima pašalinti."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Aplankas be pavadinimo"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"%1$d pagrindinis ekranas"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%1$d psl. iš %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d pagrindinis ekranas iš %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d programų psl. iš %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d valdiklių psl. iš %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Sveiki!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Jauskitės kaip namie."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Sukurkite daugiau programų ir aplankų ekrano kopijų"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Tvarkykite savo vietą"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Palieskite ir laikykite foną, jei norite tvarkyti ekrano foną, valdiklius ir nustatymus."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Pasirinkite kelias programas"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Jei norite prie pagrindinio ekrano pridėti programą, palieskite ją ir laikykite."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Štai aplankas"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Kad sukurtumėte tokį patį, palieskite ir laikykite programą, tada perkelkite ją virš kitos programos."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Gerai"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Atidarytas aplankas, <xliff:g id="WIDTH">%1$d</xliff:g> ir <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Palieskite, kad uždarytumėte aplanką"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Palieskite, kad išsaugotumėte naują pavadinimą"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Aplankas uždarytas"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Aplankas pervardytas kaip „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Valdikliai"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Ekrano fonai"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Nustatymai"</string>
+</resources>
diff --git a/res/values-lv-land/strings.xml b/res/values-lv-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-lv-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..b70f5dc
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Sākums"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android pamatlietotnes"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Iestatīt fona tapeti"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Atlasīti: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Atlasīti: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Atlasīti: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d. fona tapete no %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Atlasīts: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Dzēst"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Izvēlēties attēlu"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Fona tapetes"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Apgriezt fona tapeti"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Logrīki"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Logrīki"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Rādīt atmiņu"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Lai izvēlētos logrīku, pieskarieties un turiet to."</string>
+    <string name="market" msgid="2619650989819296998">"Iepirkties"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Nevarēja nomest vienumu šajā sākuma ekrānā."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Izveidojamā logrīka izvēle"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mapes nosaukums"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Mapes pārdēvēšana"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Labi"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Atcelt"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Pievienošana sākuma ekrānam"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Lietotnes"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Saīsnes"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Logrīki"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Sākuma ekrānos vairs nav vietas."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Režīmā “hotseat” vairs nav vietas."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Šis logrīks ir pārāk liels režīmam “hotseat”."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Tika izveidota saīsne “<xliff:g id="NAME">%s</xliff:g>”."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Tika noņemta saīsne “<xliff:g id="NAME">%s</xliff:g>”."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Saīsne “<xliff:g id="NAME">%s</xliff:g>” jau pastāv."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Saīsnes izvēle"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Lietotnes izvēle"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Lietotnes"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Sākums"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Noņemt"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Atinstalēt"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Noņemt"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Atinstalēt"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Lietotnes informācija"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Meklēt"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Meklēšana ar balsi"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Lietotnes"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Noņemt"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Atinstalēt atjauninājumu"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Atinstalēt lietotni"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Lietotnes informācija"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Atlasīta 1 lietotne"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Atlasīts 1 logrīks"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Atlasīta 1 mape"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Atlasīta 1 saīsne"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalēt saīsnes"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"atinstalēt saīsnes"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ļauj lietotnei noņemt saīsnes, nejautājot lietotājam."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lasīt sākuma ekrāna iestatījumus un saīsnes"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Ļauj lietotnei lasīt iestatījumus un saīsnes sākuma ekrānā."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"rakstīt sākuma ekrāna iestatījumus un saīsnes"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Ļauj lietotnei mainīt iestatījumus un saīsnes sākuma ekrānā."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Ielādējot logrīku, radās problēma."</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Šī ir sistēmas lietotne, un to nevar atinstalēt."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Mape bez nosaukuma"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Sākuma ekrāns: %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. lapa no %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Sākuma ekrāns: %1$d no %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d. lietotņu lapa no %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d. logrīku lapa no %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Laipni lūdzam!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Informācija par pamatfunkcijām"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Izveidojiet vairāk ekrānu lietotnēm un mapēm."</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Kārtojiet savu darbvietu"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Pieskarieties fonam un turiet to, lai pārvaldītu fona tapeti, logrīkus un iestatījumus."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Izvēlieties dažas lietotnes"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Lai sākuma ekrānam pievienotu lietotni, pieskarieties tai un turiet to."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Lūk, mape!"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Lai izveidotu tādu pašu, pieskarieties lietotnei un turiet to, pēc tam pārvietojiet to virs citas lietotnes."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Labi"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Atvērta mape: <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Pieskarieties, lai aizvērtu mapi."</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Pieskarieties, lai saglabātu pārdēvēto nosaukumu."</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mape aizvērta"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mape pārdēvēta par: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mape: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Logrīki"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Fona tapetes"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Iestatījumi"</string>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..7cf7a9c
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Нүүр"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Андройд үндсэн апп"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Ханын зургийг тохируулах"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d сонгогдсон"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d сонгогдсон"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d сонгогдсон"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%2$d ханын цаасны %1$d нь"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> сонгогдсон"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Устгах"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Зураг сонгох"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Ханын зураг"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Ханын зургийг тайрах"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Апп суугаагүй байна."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Виджет"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Виджет"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Мем харуулах"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетийг авах бол хүрээд барина уу."</string>
+    <string name="market" msgid="2619650989819296998">"Дэлгүүр"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Энэ Нүүр дэлгэцэнд буулгах боломжгүй."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Үүсгэх виджетээ сонгоно уу"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Фолдерын нэр"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Фолдерын нэр өөрчлөх"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Тийм"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Цуцлах"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Нүүр дэлгэцэнд нэмэх"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Апп"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Товчлол"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Виджет"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Таны Нүүр дэлгэц зайгүй."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Суурь зайгүй."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Энэ виджет сууринд хэт томдож байна."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол үүсэв."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол устгагдав."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол өмнө үүссэн байна."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Товчлол сонгох"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Апп сонгох"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Апп"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Нүүр"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Устгах"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Устгах"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Устгах"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Устгах"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Апп мэдээлэл"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Хайх"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Дуун хайлт"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Апп"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Устгах"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Шинэчлэлийг устгах"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Апп устгах"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Апп дэлгэрэнгүй"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 апп сонгогдсон"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 виджет сонгогдсон"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 фолдер сонгогдсон"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 товчлол сонгогдсон"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"товчлол суулгах"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"товчлолыг устгах"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг устгаж чадна"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"Нүүрний тохиргоо болон товчлолыг унших"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Апп нь Нүүрэндэх товчлол болон тохиргоог уншиж чадна."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"Нүүрний тохиргоо болон товчлолыг бичих"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Апп нь Нүүрэндэх товчлол болон тохиргоог өөрчилж чадна."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Виджет ачаалахад асуудал гарав"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Энэ апп нь системийн апп ба устгах боломжгүй."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Пуужин хөөргөгч"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Нэргүй фолдер"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Нүүр дэлгэц %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%2$d-н %1$d хуудас"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d-н Нүүр дэлгэц %1$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%2$d-н %1$d апп хуудас"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%2$d-н %1$d виджет хуудас"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Тавтай морилно уу!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Гэртээ байгаа мэт тухлаарай."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Апп болон фолдеруудад зориулан өөр дэлгэцүүд үүсгээрэй"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Өөрийнхөө зайг тохируулаарай"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Арын дэвсгэр дээр хүрээд &amp; дарснаар ханын зураг, виджет болон тохиргоог өөрчилж болно."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Апп сонгоно уу"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Нүүр дэлгэцэнд апп нэмэх бол хүрээд барина уу."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Фолдер энд байна"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Үүнтэй адилханыг үүсгэхийн тулд апп дээр хүрч &amp; бариад нөгөөхийн дээр зөөнө үү."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Тийм"</string>
+    <string name="folder_opened" msgid="94695026776264709">"<xliff:g id="WIDTH">%1$d</xliff:g> <xliff:g id="HEIGHT">%2$d</xliff:g> фолдер нээгдэв"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Фолдер хаах бол хүрнэ үү"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Шинэ нэрийг хадгалах бол хүрнэ үү"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Фолдер хаагдав"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Фолдерын нэр <xliff:g id="NAME">%1$s</xliff:g> болов"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Виджет"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Ханын зураг"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Тохиргоо"</string>
+</resources>
diff --git a/res/values-ms-land/strings.xml b/res/values-ms-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ms-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..4ee3545
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Laman Utama"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Apl Teras Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Tetapkan kertas dinding"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d dipilih"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d dipilih"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d dipilih"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Kertas dinding %1$d daripada %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Memilih <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Padam"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pilih imej"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Kertas dinding"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Pangkas kertas dinding"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Papar Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh &amp; tahan untuk mengambil widget."</string>
+    <string name="market" msgid="2619650989819296998">"Beli-belah"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Tidak dapat melepaskan item pada Skrin Utama."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pilih widget yang hendak dibuat"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nama folder"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Namakan semula folder"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Batal"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Tambah ke skrin Laman Utama"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apl"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Pintasan"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Tiada lagi ruang pada skrin Laman Utama anda."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Tiada lagi ruang pada skrin Laman Utama ini."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Tiada lagi ruang pada kerusi panas."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget ini terlalu besar untuk kerusi panas."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dibuat."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dialih keluar."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah wujud."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Pilih jalan pintas"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Pilih apl"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apl"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Laman Utama"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Alih keluar"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Nyahpasang"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Alih keluar"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Nyahpasang"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Maklumat apl"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Cari"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Carian Suara"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apl"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Alih keluar"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Nyahpasang kemas kini"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Nyahpasang apl"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Butiran apl"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 apl dipilih"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget dipilih"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder dipilih"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pintasan dipilih"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"pasang pintasan"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Membenarkan apl menambah pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"nyahpasang pintasan"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"baca tetapan dan pintasan Laman Utama"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Membenarkan apl membaca tetapan dan pintasan di Laman Utama."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"tulis tetapan dan pintasan Laman Utama"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Membenarkan apl menukar tetapan dan pintasan di Laman Utama."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Masalah memuatkan widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ini ialah apl sistem dan tidak boleh dinyahpasang."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Pelancar Roket"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Folder Tanpa Nama"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Skrin Laman Utama %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Halaman %1$d daripada %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Skrin Laman Utama %1$d daripada %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Halaman apl %1$d daripada %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Halaman widget %1$d daripada %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Selamat datang!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Buat seperti berada di rumah sendiri."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Buat lebih banyak skrin untuk apl dan folder"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Susun ruang anda"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Sentuh &amp; tahan latar belakang untuk mengurus kertas dinding, widget dan tetapan."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Pilih beberapa apl"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Untuk menambahkan apl pada skrin Laman Utama anda, sentuh &amp; tahan apl."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ini ada folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Untuk membuat satu folder seperti ini, sentuh &amp; tahan apl, kemudian alihkan ke atas folder lain."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Sentuh untuk menutup folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Sentuh untuk menyimpan penamaan semula"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Kertas dinding"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Tetapan"</string>
+</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
new file mode 100644
index 0000000..d2f6bb1
--- /dev/null
+++ b/res/values-ms/strings.xml
@@ -0,0 +1,115 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="8424725141379931883">"Pelancar"</string>
+    <string name="home" msgid="5921706419368316758">"Laman Utama"</string>
+    <string name="uid_name" msgid="3371120195364560632">"Apl Teras Android"</string>
+    <string name="folder_name" msgid="8551881338202938211"></string>
+    <string name="chooser_wallpaper" msgid="6063168087625352235">"Pilih kertas dinding dari"</string>
+    <string name="wallpaper_instructions" msgid="4215640646180727542">"Tetapkan kertas dinding"</string>
+    <string name="pick_wallpaper" msgid="5630222540525626723">"Kertas dinding"</string>
+    <string name="activity_not_found" msgid="217823393239365967">"Aplikasi tidak dipasang."</string>
+    <string name="widgets_tab_label" msgid="9145860100000983599">"Widget"</string>
+    <string name="long_press_widget_to_add" msgid="7395697462851217506">"Sentuh &amp; tahan untuk mengambil widget."</string>
+    <string name="market" msgid="2652226429823445833">"Kedai"</string>
+    <string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="2285187188524172774">"Tidak dapat melepaskan item pada skrin Utama ini."</string>
+    <string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Pilih widget untuk dibuat"</string>
+    <string name="rename_folder_label" msgid="5646236631298452787">"Nama folder"</string>
+    <string name="rename_folder_title" msgid="4544573104191526550">"Namakan semula folder"</string>
+    <string name="rename_action" msgid="6016003384693240896">"OK"</string>
+    <string name="cancel_action" msgid="3811860427489435048">"Batal"</string>
+    <string name="menu_item_add_item" msgid="6233177331075781114">"Tambah ke Skrin utama"</string>
+    <string name="group_applications" msgid="2103752818818161976">"Aplikasi"</string>
+    <string name="group_shortcuts" msgid="9133529424900391877">"Pintasan"</string>
+    <string name="group_widgets" msgid="6704978494073105844">"Widget"</string>
+    <string name="group_wallpapers" msgid="1568191644272224858">"Kertas dinding"</string>
+    <string name="completely_out_of_space" msgid="1759078539443491182">"Tiada lagi ruang pada skrin Utama anda."</string>
+    <string name="out_of_space" msgid="8365249326091984698">"Tiada lagi ruang pada skrin Utama ini"</string>
+    <string name="hotseat_out_of_space" msgid="6304886797358479361">"Tiada lagi ruang pada kerusi panas."</string>
+    <string name="invalid_hotseat_item" msgid="6545340627805449250">"Widget ini terlalu besar untuk kerusi panas."</string>
+    <string name="shortcut_installed" msgid="7071557296331322355">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" diwujudkan."</string>
+    <string name="shortcut_uninstalled" msgid="2129499669449749995">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dialih keluar."</string>
+    <string name="shortcut_duplicate" msgid="4757756326465060694">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah pun wujud."</string>
+    <string name="title_select_shortcut" msgid="1873670208166882222">"Pilih pintasan"</string>
+    <string name="title_select_application" msgid="1793455815754848652">"Pilih aplikasi"</string>
+    <string name="all_apps_button_label" msgid="2578400570124163469">"Apl"</string>
+    <string name="all_apps_home_button_label" msgid="1022222300329398558">"Laman Utama"</string>
+    <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Alih keluar"</string>
+    <string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Nyahpasang"</string>
+    <string name="delete_target_label" msgid="665300185123139530">"Alih keluar"</string>
+    <string name="delete_target_uninstall_label" msgid="748894921183769150">"Nyahpasang"</string>
+    <string name="info_target_label" msgid="4019495079517426980">"Maklumat apl"</string>
+    <string name="accessibility_search_button" msgid="816822994629942611">"Carian"</string>
+    <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Carian Suara"</string>
+    <string name="accessibility_all_apps_button" msgid="8803738611398979849">"Aplikasi"</string>
+    <string name="accessibility_delete_button" msgid="3628162007991023603">"Alih keluar"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Nyahpasang kemas kini"</string>
+    <string name="menu_add" msgid="3065046628354640854">"Tambah"</string>
+    <string name="menu_manage_apps" msgid="2308685199463588895">"Mengurus apl"</string>
+    <string name="menu_wallpaper" msgid="5837429080911269832">"Kertas dinding"</string>
+    <string name="menu_search" msgid="4826514464423239041">"Cari"</string>
+    <string name="menu_notifications" msgid="6424587053194766192">"Pemberitahuan"</string>
+    <string name="menu_settings" msgid="3946232973327980394">"Tetapan sistem"</string>
+    <string name="menu_help" msgid="4901160661634590633">"Bantuan"</string>
+    <string name="cab_menu_delete_app" msgid="4089398025537640349">"Nyahpasang aplikasi"</string>
+    <string name="cab_menu_app_info" msgid="914548323652698884">"Butiran aplikasi"</string>
+    <string name="cab_app_selection_text" msgid="6378522164293415735">"1 aplikasi dipilih"</string>
+    <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget dipilih"</string>
+    <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 folder dipilih"</string>
+    <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 pintasan dipilih"</string>
+    <string name="permlab_install_shortcut" msgid="1201690825493376489">"pasang pintasan"</string>
+    <string name="permdesc_install_shortcut" msgid="8634424803272077038">"Membenarkan aplikasi menambah pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"nyahpasang pintasan"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."</string>
+    <string name="permlab_read_settings" msgid="3452408290738106747">"membaca tetapan dan pintasan Laman Utama"</string>
+    <string name="permdesc_read_settings" msgid="5788109303585403679">"Membenarkan apl membaca tetapan dan pintasan di Laman Utama."</string>
+    <string name="permlab_write_settings" msgid="1360567537236705628">"menulis tetapan dan pintasan Laman Utama"</string>
+    <string name="permdesc_write_settings" msgid="8530105489115785531">"Membenarkan apl menukar tetapan dan pintasan di Laman Utama."</string>
+    <string name="gadget_error_text" msgid="8359351016167075858">"Masalah memuatkan widget"</string>
+    <string name="uninstall_system_app_text" msgid="6429814133777046491">"Ini adalah aplikasi sistem dan tidak boleh dinyahpasang."</string>
+    <string name="dream_name" msgid="2847171357608437154">"Pelancar Roket"</string>
+    <string name="folder_hint_text" msgid="8633351560105748141">"Folder Tanpa Nama"</string>
+    <string name="workspace_description_format" msgid="2968608205939373034">"Skrin utama %1$d"</string>
+    <string name="default_scroll_format" msgid="4057140866420001240">"Halaman %1$d dari %2$d"</string>
+    <string name="workspace_scroll_format" msgid="1704767047951143301">"Skrin utama %1$d dari %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"Halaman apl %1$d dari %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Halaman widget %1$d dari %2$d"</string>
+    <string name="workspace_cling_title" msgid="738396473989890567">"Buat diri anda seperti di rumah"</string>
+    <string name="workspace_cling_move_item" msgid="791013895761065070">"Anda boleh meletakkan aplikasi kegemaran anda di sini."</string>
+    <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Untuk melihat semua aplikasi anda, sentuh bulatan."</string>
+    <string name="all_apps_cling_title" msgid="2559734712581447107">"Pilih beberapa aplikasi"</string>
+    <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Untuk menambahkan aplikasi pada skrin Utama anda, sentuh &amp; tahankannya."</string>
+    <string name="folder_cling_title" msgid="4308949882377840953">"Susun aplikasi anda dengan folder"</string>
+    <string name="folder_cling_move_item" msgid="270598675060435169">"Untuk memindahkan aplikasi, sentuh &amp; tahankannya."</string>
+    <string name="folder_cling_create_folder" msgid="8352867485656129478">"Untuk membuat folder baharu pada skrin Utama anda, tindihkan satu aplikasi di atas yang lain."</string>
+    <string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
+    <string name="folder_opened" msgid="1262064100943801533">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1335478160661137579">"Sentuh untuk menutup folder"</string>
+    <string name="folder_tap_to_rename" msgid="5201612989905472442">"Sentuh untuk menyimpan penamaan semula"</string>
+    <string name="folder_closed" msgid="3130534551370511932">"Folder ditutup"</string>
+    <string name="folder_renamed" msgid="7951233572858053642">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="3051680259794759037">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
+    <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
+    <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
+    <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
+</resources>
diff --git a/res/values-nb-land/strings.xml b/res/values-nb-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-nb-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..ce50754
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Startside"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Kjerneapper for Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Angi bakgrunn"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d valgt"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d valgt"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d valgt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Bakgrunn %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Valgt <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Slett"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Velg bilde"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Bakgrunner"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Beskjær bakgrunnen"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Moduler"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Moduler"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Vis minne"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Trykk og hold inne for å plukke opp en modul."</string>
+    <string name="market" msgid="2619650989819296998">"Butikk"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Kunne ikke slippe elementet på denne startsiden."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Velg modul for oppretting"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mappenavn"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Gi mappen nytt navn"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Avbryt"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Legg til på startsiden"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apper"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Snarveier"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Moduler"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Ikke mer plass på startsidene dine."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Dokksonen er full."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denne modulen er for stor for dokksonen."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» er opprettet."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» er fjernet."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» fins allerede."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Valg av snarvei"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Velg app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apper"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startside"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Fjern"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Avinstaller"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Fjern"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Avinstaller"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"App-info"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Søk"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Talesøk"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apper"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Fjern"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Avinstaller oppdateringen"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Avinstaller appen"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Informasjon om appen"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Én app er valgt"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Én modul er valgt"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Én mappe er valgt"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Én snarvei er valgt"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installere snarveier"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"avinstallere snarveier"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Lar appen fjerne snarveier uten innblanding fra brukeren."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"lese startsideinnstillinger og -snarveier"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Lar appen lese innstillingene og snarveiene på startsiden."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"angi startsideinnstillinger og -snarveier"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Lar appen endre innstillingene og snarveiene på startsiden."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem ved innlasting av modul"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dette er en systemapp som ikke kan avinstalleres."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Mappe uten navn"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Startside %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d av %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Startside %1$d av %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Appside %1$d av %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Modulside %1$d av %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Velkommen!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Føl deg som hjemme."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Opprett flere sider for apper og mapper"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser plassen din"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Trykk og hold på bakgrunnen for å administrere bakgrunnen, moduler og innstillinger."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Velg noen apper"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Trykk og hold på en app for å legge den til på startsiden."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Dette er en mappe"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"For å opprette en som denne, trykker og holder du på en app og flytter den over en annen."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mappen er åpnet – <xliff:g id="WIDTH">%1$d</xliff:g> ganger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Trykk for å lukke mappen"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Trykk for å lagre det nye navnet"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mappen ble lukket"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mappen heter nå <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Moduler"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunner"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Innstillinger"</string>
+</resources>
diff --git a/res/values-nl-land/strings.xml b/res/values-nl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-nl-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..e2a2a07
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Startpagina"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android-kernapps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Achtergrond instellen"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d geselecteerd"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d geselecteerd"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d geselecteerd"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Achtergrond %1$d van %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> is geselecteerd"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Verwijderen"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Afbeelding kiezen"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Achtergronden"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Achtergrond bijsnijden"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Geheugen weergeven"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Blijf aanraken om een widget toe te voegen."</string>
+    <string name="market" msgid="2619650989819296998">"Winkelen"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Kan item niet neerzetten in dit startscherm."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Widget selecteren om te maken"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mapnaam"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Naam van map wijzigen"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Annuleren"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Toevoegen aan startscherm"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Snelkoppelingen"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Er is geen ruimte meer op uw startschermen."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Er is geen ruimte meer op de hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Deze widget is te groot voor de hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' is gemaakt."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' is verwijderd."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' bestaat al."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Snelkoppeling selecteren"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"App selecteren"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startpagina"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Verwijderen"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Verwijderen"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Verwijderen"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Verwijderen"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"App-info"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Zoeken"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Gesproken zoekopdracht"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Verwijderen"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Update verwijderen"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"App verwijderen"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"App-details"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app geselecteerd"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget geselecteerd"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 map geselecteerd"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 snelkoppeling geselecteerd"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"snelkoppelingen installeren"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"snelkoppelingen verwijderen"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"De app toestaan snelkoppelingen te verwijderen zonder tussenkomst van de gebruiker."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de startpagina lezen"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de startpagina te lezen."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de startpagina schrijven"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de startpagina te wijzigen."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Probleem bij het laden van widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dit is een systeemapp die niet kan worden verwijderd."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Naamloze map"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Startscherm %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d van %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Startscherm %1$d van %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"App-pagina %1$d van %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgetpagina %1$d van %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Welkom!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Personaliseer uw startscherm."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Meer schermen maken voor apps en mappen"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Uw ruimte indelen"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Blijf de achtergrond aanraken om de achtergrond, widgets en instellingen te beheren."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Selecteer een aantal apps"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Als u een app wilt toevoegen aan het startscherm, blijft u de app aanraken."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Dit is een map"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Als u een map zoals deze wilt maken, blijft u een app aanraken en schuift u deze boven op een andere app."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Map geopend, <xliff:g id="WIDTH">%1$d</xliff:g> bij <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Raak dit aan om de map te sluiten"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Raak dit aan om de gewijzigde naam op te slaan"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Map gesloten"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"De naam van de map is gewijzigd in <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Map: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Achtergronden"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Instellingen"</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-pl-land/strings.xml b/res/values-pl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pl-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..b629f3d
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Ekran główny"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Główne aplikacje Androida"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Ustaw tapetę"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Wybrane: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Wybrane: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Wybrane: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Wybrano <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Usuń"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Wybierz obraz"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Przytnij tapetę"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widżety"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widżety"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Pokaż pamięć"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Aby dodać widżet, kliknij go i przytrzymaj."</string>
+    <string name="market" msgid="2619650989819296998">"Sklep"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Nie można upuścić elementu na tym ekranie głównym."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Wybierz widżet, który chcesz dodać"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nazwa folderu"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Zmień nazwę folderu"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Anuluj"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Dodaj do ekranu głównego"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikacje"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Skróty"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widżety"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Brak miejsca na ekranach głównych."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Brak miejsca w kieszonce."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ten widżet jest za duży, by umieścić go w kieszonce."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Skrót „<xliff:g id="NAME">%s</xliff:g>” został utworzony."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Skrót „<xliff:g id="NAME">%s</xliff:g>” został usunięty."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Skrót „<xliff:g id="NAME">%s</xliff:g>” już istnieje."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Wybierz skrót"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Wybierz aplikację"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacje"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Ekran główny"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Usuń"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinstaluj"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Usuń"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinstaluj"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informacje o aplikacji"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Szukaj"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Wyszukiwanie głosowe"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacje"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Usuń"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinstaluj aktualizację"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinstaluj aplikację"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Szczegóły aplikacji"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Wybrano 1 aplikację"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Wybrano 1 widżet"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Wybrano 1 folder"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Wybrano 1 skrót"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalowanie skrótów"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pozwala aplikacji dodawać skróty bez interwencji użytkownika."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinstalowywanie skrótów"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Pozwala aplikacji usuwać skróty bez interwencji użytkownika."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"odczytywanie ustawień i skrótów na ekranie głównym"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Pozwala aplikacji na odczytywanie ustawień i skrótów na ekranie głównym."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"zapisywanie ustawień i skrótów na ekranie głównym"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Umożliwia aplikacji zmianę ustawień i skrótów na ekranie głównym."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem podczas ładowania widżetu"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"To aplikacja systemowa i nie można jej odinstalować."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Wyrzutnia rakiet"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Folder bez nazwy"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Ekran główny %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Strona %1$d z %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Ekran główny %1$d z %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Strona aplikacji: %1$d z %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Strona widżetów: %1$d z %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Witamy"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Poczuj się jak u siebie."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Dodaj więcej ekranów na aplikacje i foldery"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Uporządkuj obszar roboczy"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Kliknij i przytrzymaj tło, by zmienić tapetę, widżety lub ustawienia."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Wybierz kilka aplikacji"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Aby dodać aplikację na ekran główny, dotknij i przytrzymaj jej ikonę."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Tu jest folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Aby utworzyć taki sam, kliknij i przytrzymaj aplikację, a następnie przenieś ją na następną."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder otwarty, <xliff:g id="WIDTH">%1$d</xliff:g> na <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Kliknij, by zamknąć folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Kliknij, by zapisać zmianę nazwy"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder zamknięty"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Nazwa folderu zmieniona na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widżety"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Ustawienia"</string>
+</resources>
diff --git a/res/values-port/dimens.xml b/res/values-port/dimens.xml
new file mode 100644
index 0000000..7194a2a
--- /dev/null
+++ b/res/values-port/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+<!-- Workspace -->
+    <dimen name="workspace_page_spacing">-1dp</dimen>
+
+<!-- AppsCustomize -->
+    <integer name="apps_customize_cling_focused_x">1</integer>
+    <integer name="apps_customize_cling_focused_y">1</integer>
+
+    <integer name="apps_customize_widget_cell_count_x">2</integer>
+    <integer name="apps_customize_widget_cell_count_y">3</integer>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-port/styles.xml
new file mode 100644
index 0000000..ab6a1eb
--- /dev/null
+++ b/res/values-port/styles.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+-->
+
+<resources>
+<!-- AppsCustomize -->
+    <style name="TabIndicator.AppsCustomize">
+        <item name="android:maxWidth">130dp</item>
+    </style>
+</resources>
diff --git a/res/values-pt-land/strings.xml b/res/values-pt-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pt-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pt-rPT-land/strings.xml b/res/values-pt-rPT-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pt-rPT-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..1ef3982
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Iniciador3"</string>
+    <string name="home" msgid="7658288663002113681">"Ecrã principal"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Aplicações principais do Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Definir imagem fundo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selecionado(s)"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selecionado(s)"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selecionado(s)"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Imagem de fundo %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> selecionado"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Escolher imagem"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Imagens de fundo"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar imagem de fundo"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"A aplicação não está instalada."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Prima sem soltar para escolher um widget."</string>
+    <string name="market" msgid="2619650989819296998">"Comprar"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Não foi possível largar o item neste Ecrã Principal."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Escolher um widget para criar"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nome da pasta"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Mudar o nome da pasta"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Adicionar ao Ecrã principal"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicações"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Atalhos"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Sem espaço suficiente nos Ecrãs principais."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Sem espaço suficiente na barra personalizável."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget é demasiado grande para a barra personalizável."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Atalho “<xliff:g id="NAME">%s</xliff:g>” criado."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"O atalho “<xliff:g id="NAME">%s</xliff:g>” foi removido."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"O atalho “<xliff:g id="NAME">%s</xliff:g>” já existe."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Escolher atalho"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Escolher aplicação"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicações"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecrã principal"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remover"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Remover"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informações da aplicação"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Pesquisar"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Pesquisa por Voz"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicações"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Remover"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar atualização"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar a aplicação"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalhes da aplicação"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicação selecionada"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selecionado"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 pasta selecionada"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 atalho selecionado"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite a uma aplicação adicionar atalhos sem a intervenção do utilizador."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar atalhos"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite à aplicação remover atalhos sem intervenção do utilizador."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"ler definições e atalhos do Ecrã Principal"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite à aplicação ler as definições e os atalhos no Ecrã Principal."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"escrever definições e atalhos do Ecrã principal"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite à aplicação alterar as definições e os atalhos no Ecrã Principal."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema ao carregar o widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"É uma aplicação de sistema e não pode ser desinstalada."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Lança-mísseis"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Pasta sem nome"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Ecrã principal %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecrã principal %1$d de %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicações %1$d de %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Bem-vindo(a)!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Sinta-se em casa."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crie mais ecrãs para aplicações e pastas"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizar o seu espaço"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Toque sem soltar no fundo para gerir a imagem de fundo, os widgets e as definições."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Escolher algumas aplicações"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para adicionar uma aplicação ao Ecrã principal, toque na mesma sem soltar."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Eis uma pasta"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para criar uma pasta, toque sem soltar numa aplicação e arraste-a para cima de outra aplicação."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toque para fechar a pasta"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toque para guardar o nome novo"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Pasta fechada"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Nome de pasta alterado para <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagens de fundo"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Definições"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..4d40388
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Início"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Principais aplicativos do Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Definir plano de fundo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selecionados"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selecionados"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selecionados"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Plano de fundo %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> selecionado"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Excluir"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Escolher imagem"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Planos de fundo"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Cortar plano de fundo"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"O aplicativo não está instalado."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memória"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Toque e pressione para selecionar um widget."</string>
+    <string name="market" msgid="2619650989819296998">"Comprar"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Não foi possível soltar o item nesta tela inicial."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Selecione um widget para criar"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Nome da pasta"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Renomear pasta"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Ok"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Adicionar à tela inicial"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicativos"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Atalhos"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Não há mais espaço nas telas iniciais."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Não há mais espaço no hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget é muito grande para o hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Atalho \"<xliff:g id="NAME">%s</xliff:g>\" criado."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"O atalho \"<xliff:g id="NAME">%s</xliff:g>\" foi removido."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"O atalho \"<xliff:g id="NAME">%s</xliff:g>\" já existe."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Selecione um atalho"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Selecione um aplicativo"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicativos"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Início"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remover"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Remover"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informações do aplicativo"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Pesquisar"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Pesquisa por voz"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicativos"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Remover"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar atualização"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicativo"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalhes do aplicativo"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Um aplicativo selecionado"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Um widget selecionado"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Uma pasta selecionada"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Um atalho selecionado"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que um aplicativo adicione atalhos sem intervenção do usuário."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar atalhos"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que o aplicativo remova atalhos sem a intervenção do usuário."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"ler configurações e atalhos da tela inicial"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que o aplicativo leia as configurações e os atalhos na tela inicial."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"gravar configurações e atalhos da tela inicial"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que o aplicativo altere as configurações e os atalhos na tela inicial."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema ao carregar o widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Este é um aplicativo do sistema e não pode ser desinstalado."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Pasta sem nome"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Tela inicial %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Tela inicial %1$d de %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicativos, %1$d de %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets, %1$d de %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Bem-vindo!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Fique à vontade."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crie mais telas para aplicativos e pastas"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organize seu espaço"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Toque e mantenha pressionada a tela de fundo para gerenciar o plano de fundo, os widgets e as configurações."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Escolha alguns aplicativos"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para adicionar um aplicativo a sua tela inicial, toque e mantenha-o pressionado."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Aqui está uma pasta"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para criar uma pasta como esta, mantenha pressionado um aplicativo e mova-o para cima de outro."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Ok"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Toque para fechar a pasta"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toque para salvar o novo nome"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Pasta fechada"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Pasta renomeada para <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Planos de fundo"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Configurações"</string>
+</resources>
diff --git a/res/values-rm-land/strings.xml b/res/values-rm-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-rm-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
new file mode 100644
index 0000000..ddcf404
--- /dev/null
+++ b/res/values-rm/strings.xml
@@ -0,0 +1,212 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for application_name (5181331383435256801) -->
+    <skip />
+    <!-- no translation found for home (7658288663002113681) -->
+    <skip />
+    <!-- no translation found for uid_name (7820867637514617527) -->
+    <skip />
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <!-- no translation found for wallpaper_instructions (563973358787555519) -->
+    <skip />
+    <!-- no translation found for number_of_items_selected:zero (7464587177007785408) -->
+    <!-- no translation found for number_of_items_selected:one (142482526010824029) -->
+    <!-- no translation found for number_of_items_selected:other (1418352074806573570) -->
+    <!-- no translation found for wallpaper_accessibility_name (1655953108132967972) -->
+    <skip />
+    <!-- no translation found for announce_selection (8338254712932127413) -->
+    <skip />
+    <!-- no translation found for wallpaper_delete (8095005658756613921) -->
+    <skip />
+    <!-- no translation found for pick_image (1272073934062909527) -->
+    <skip />
+    <!-- no translation found for pick_wallpaper (8179698221502010609) -->
+    <skip />
+    <!-- no translation found for crop_wallpaper (8334345984491368009) -->
+    <skip />
+    <!-- no translation found for activity_not_found (8071924732094499514) -->
+    <skip />
+    <!-- no translation found for widgets_tab_label (2921133187116603919) -->
+    <skip />
+    <!-- no translation found for widget_adder (3201040140710381657) -->
+    <skip />
+    <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
+    <skip />
+    <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
+    <skip />
+    <!-- no translation found for market (2619650989819296998) -->
+    <skip />
+    <!-- no translation found for widget_dims_format (2370757736025621599) -->
+    <skip />
+    <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
+    <skip />
+    <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
+    <skip />
+    <!-- no translation found for rename_folder_label (3727762225964550653) -->
+    <skip />
+    <!-- no translation found for rename_folder_title (3771389277707820891) -->
+    <skip />
+    <!-- no translation found for rename_action (5559600076028658757) -->
+    <skip />
+    <!-- no translation found for cancel_action (7009134900002915310) -->
+    <skip />
+    <!-- no translation found for menu_item_add_item (1264911265836810421) -->
+    <skip />
+    <!-- no translation found for group_applications (3797214114206693605) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (6012256992764410535) -->
+    <skip />
+    <!-- no translation found for group_widgets (1569030723286851002) -->
+    <skip />
+    <!-- no translation found for completely_out_of_space (6106288382070760318) -->
+    <skip />
+    <!-- no translation found for out_of_space (4691004494942118364) -->
+    <skip />
+    <!-- no translation found for hotseat_out_of_space (9139760413395605841) -->
+    <skip />
+    <!-- no translation found for invalid_hotseat_item (1211534262129849507) -->
+    <skip />
+    <!-- no translation found for shortcut_installed (1701742129426969556) -->
+    <skip />
+    <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
+    <skip />
+    <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
+    <skip />
+    <!-- no translation found for title_select_shortcut (6680642571148153868) -->
+    <skip />
+    <!-- no translation found for title_select_application (3280812711670683644) -->
+    <skip />
+    <!-- no translation found for all_apps_button_label (9110807029020582876) -->
+    <skip />
+    <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
+    <skip />
+    <!-- no translation found for delete_target_label (1822697352535677073) -->
+    <skip />
+    <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
+    <skip />
+    <!-- no translation found for info_target_label (8053346143994679532) -->
+    <skip />
+    <!-- no translation found for accessibility_search_button (1628520399424565142) -->
+    <skip />
+    <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
+    <skip />
+    <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
+    <skip />
+    <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
+    <skip />
+    <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
+    <skip />
+    <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
+    <skip />
+    <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
+    <skip />
+    <!-- no translation found for cab_app_selection_text (374688303047985416) -->
+    <skip />
+    <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
+    <skip />
+    <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
+    <skip />
+    <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
+    <skip />
+    <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
+    <skip />
+    <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
+    <skip />
+    <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
+    <skip />
+    <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
+    <skip />
+    <!-- no translation found for permlab_read_settings (1941457408239617576) -->
+    <skip />
+    <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
+    <skip />
+    <!-- no translation found for permlab_write_settings (3574213698004620587) -->
+    <skip />
+    <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
+    <skip />
+    <!-- no translation found for gadget_error_text (6081085226050792095) -->
+    <skip />
+    <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
+    <skip />
+    <!-- no translation found for dream_name (1530253749244328964) -->
+    <skip />
+    <!-- no translation found for folder_hint_text (6617836969016293992) -->
+    <skip />
+    <!-- no translation found for workspace_description_format (2950174241104043327) -->
+    <skip />
+    <!-- no translation found for default_scroll_format (7475544710230993317) -->
+    <skip />
+    <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+    <skip />
+    <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
+    <skip />
+    <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
+    <skip />
+    <!-- no translation found for first_run_cling_title (7257389003637362144) -->
+    <skip />
+    <!-- no translation found for first_run_cling_description (6447072552696253358) -->
+    <skip />
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
+    <skip />
+    <!-- no translation found for workspace_cling_title (5626202359865825661) -->
+    <skip />
+    <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
+    <skip />
+    <!-- no translation found for all_apps_cling_title (34929250753095858) -->
+    <skip />
+    <!-- no translation found for all_apps_cling_add_item (400866858451850784) -->
+    <skip />
+    <!-- no translation found for folder_cling_title (3894908818693254164) -->
+    <skip />
+    <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
+    <skip />
+    <!-- no translation found for cling_dismiss (8962359497601507581) -->
+    <skip />
+    <!-- no translation found for folder_opened (94695026776264709) -->
+    <skip />
+    <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
+    <skip />
+    <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
+    <skip />
+    <!-- no translation found for folder_closed (4100806530910930934) -->
+    <skip />
+    <!-- no translation found for folder_renamed (1794088362165669656) -->
+    <skip />
+    <!-- no translation found for folder_name_format (6629239338071103179) -->
+    <skip />
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <!-- no translation found for widget_button_text (2880537293434387943) -->
+    <skip />
+    <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
+    <skip />
+    <!-- no translation found for settings_button_text (8119458837558863227) -->
+    <skip />
+</resources>
diff --git a/res/values-ro-land/strings.xml b/res/values-ro-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ro-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..78d9da4
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Ecran de pornire"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Setați imaginea de fundal"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d selectate"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d selectat"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d selectate"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Imaginea de fundal %1$d din %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"S-a selectat <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Ștergeți"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Alegeți imaginea"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Imagini de fundal"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Decupați imaginea de fundal"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplicația nu este instalată."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgeturi"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgeturi"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afișați memoria"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Atingeți lung un widget pentru a-l alege."</string>
+    <string name="market" msgid="2619650989819296998">"Cumpărați"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Nu se poate plasa articolul pe ecranul de pornire."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Alegeți widgetul de creat"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Numele dosarului"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Redenumiți dosarul"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Anulați"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Adăugați la Ecranul de pornire"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplicații"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Comenzi rapide"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgeturi"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Nu mai este loc pe ecranele de pornire."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Nu mai este loc în bara de lansare rapidă."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Acest widget este prea mare pentru bara de lansare rapidă."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>\" a fost creată."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>” a fost eliminată."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>” există deja."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Alegeți comanda rapidă"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Alegeți aplicația"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicații"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecran de pornire"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminați"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Dezinstalați"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Eliminați"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Dezinstalați"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informații despre aplicație"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Căutați"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Căutare vocală"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicații"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminați"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Dezinstalați actualizarea"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Dezinstalați aplicația"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalii despre aplicație"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicație selectată"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selectat"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dosar selectat"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 comandă rapidă selectată"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalează comenzi rapide"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"dezinstalează comenzi rapide"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite aplicației să elimine comenzi rapide fără intervenția utilizatorului."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"citește setări și comenzi rapide pentru ecranul de pornire"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite aplicației să citească setările și comenzile rapide din ecranul de pornire."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"scrie setări și comenzi rapide pentru ecranul de pornire"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite aplicației să modifice setările și comenzile rapide din ecranul de pornire."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problemă la încărcarea widgetului"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Aceasta este o aplicație de sistem și nu poate fi dezinstalată."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Dosar fără nume"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Ecran de pornire %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d din %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecranul de pornire %1$d din %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pagina de aplicații %1$d din %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pagina de widgeturi %1$d din %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Bun venit!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Simțiți-vă ca acasă."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Creați mai multe ecrane pentru aplicații și dosare"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizați-vă spațiul"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Atingeți lung fundalul pentru a gestiona imaginea de fundal, widgeturile și setările."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Alegeți unele aplicații"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Atingeți lung o aplicație pentru a o adăuga pe ecranul de pornire."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Iată un dosar"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pentru a crea un dosar similar, atingeți și țineți degetul pe o aplicație, apoi mutați-o deasupra alteia."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Dosar deschis, <xliff:g id="WIDTH">%1$d</xliff:g> pe <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Atingeți pentru a închide dosarul"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Atingeți pentru a salva redenumirea"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Dosar închis"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Dosar redenumit <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgeturi"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagini de fundal"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Setări"</string>
+</resources>
diff --git a/res/values-ru-land/strings.xml b/res/values-ru-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ru-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..06fbd26
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Главный экран"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Основные приложения Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Установить как обои"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Выбрано: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Выбрано: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Выбрано: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Обои %1$d из %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Выбран элемент \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Удалить"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Выбрать изображение"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Обои"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Обрезать обои"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Приложение удалено"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Виджеты"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Виджеты"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Сведения о памяти"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Чтобы выбрать виджет, нажмите на значок и удерживайте его."</string>
+    <string name="market" msgid="2619650989819296998">"Google Play"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Не удалось добавить элемент на главный экран"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Выберите виджет"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Название папки"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Переименование папки"</string>
+    <string name="rename_action" msgid="5559600076028658757">"ОК"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Отмена"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Добавление на главный экран"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Приложения"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Ярлыки"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Виджеты"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"На главных экранах все занято"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Нет свободного места в слоте"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Виджет слишком велик для слота"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" создан"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" удален"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" уже существует"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Выбор ярлыка"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Выбор приложения"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Главный экран"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Удалить"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Удалить"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Удалить"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Удалить"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"О приложении"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Поиск"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Голосовой поиск"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Приложения"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Удалить"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Удалить обновление"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Удалить приложение"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"О приложении"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Выбрано 1 приложение"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Выбран 1 виджет"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Выбрана 1 папка"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Выбран 1 ярлык"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"Создание ярлыков"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Приложение сможет самостоятельно добавлять ярлыки."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"Удаление ярлыков"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Приложение сможет самостоятельно удалять ярлыки."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"Доступ к настройкам и ярлыкам главного экрана"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Приложение получит доступ к данным о настройках и ярлыках на главном экране."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"Изменение настроек и ярлыков главного экрана"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Приложение сможет изменять настройки и ярлыки на главном экране."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Не удалось загрузить виджет"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Это системное приложение, его нельзя удалить."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Папка без названия"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Главный экран %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Стр. %1$d из %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Главные экран %1$d из %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Приложения: стр. %1$d из %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Виджеты: стр. %1$d из %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Добро пожаловать!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Будьте как дома"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Создание дополнительных экранов для приложений и папок"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Организация рабочего пространства"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Чтобы перейти к управлению обоями, виджетами и настройками, нажмите на фоновое изображение и удерживайте его."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Выберите приложения"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Чтобы добавить приложение на главный экран, нажмите на значок и удерживайте его."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Это папка"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Чтобы создать папку, нажмите и удерживайте значок приложения, а затем перетащите его на другой значок."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"ОК"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Папка открыта, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Нажмите, чтобы закрыть папку"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Нажмите, чтобы подтвердить переименование"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Папка закрыта"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Папка переименована в \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Папка: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Виджеты"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Обои"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Настройки"</string>
+</resources>
diff --git a/res/values-sk-land/strings.xml b/res/values-sk-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sk-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..29de526
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Plocha"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Nastaviť tapetu"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Počet vybratých položiek: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Počet vybratých položiek: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Počet vybratých položiek: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Vybratá položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Odstrániť"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Vybrať obrázok"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Orezanie tapety"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikácia nie je nainštalovaná."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Miniaplikácie"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Miniaplikácie"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Zobraziť pamäť"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Miniaplikáciu pridáte stlačením a podržaním."</string>
+    <string name="market" msgid="2619650989819296998">"Obchod"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Položku sa nepodarilo presunúť na túto plochu."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Zvoľte miniaplikáciu na vytvorenie"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Názov priečinka"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Premenovať priečinok"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Zrušiť"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Pridať na plochu"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikácie"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Skratky"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Miniaplikácie"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Na plochách už nie je miesto."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"V časti hotseat už nie je miesto."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Táto miniaplikácia je pre hotseat príliš veľká."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Odkaz <xliff:g id="NAME">%s</xliff:g> bol vytvorený."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Odkaz <xliff:g id="NAME">%s</xliff:g> bol odstránený."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Odkaz <xliff:g id="NAME">%s</xliff:g> už existuje."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Vybrať odkaz"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Vybrať aplikáciu"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikácie"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Domovská stránka"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstrániť"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinštalovať"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Odstrániť"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinštalovať"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Informácie o aplikácii"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Vyhľadať"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hlasové vyhľadávanie"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikácie"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstrániť"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinštalovať aktualizáciu"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinštalovať aplikáciu"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikácii"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Vybratá 1 aplikácia"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Vybratá 1 miniaplikácia"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Vybratý 1 priečinok"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Vybratý 1 odkaz"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"inštalovať odkazy"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Povoľuje aplikácii pridať odkazy bez zásahu používateľa."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinštalovať odkazy"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Povoľuje aplikácii odstrániť odkazy bez zásahu používateľa."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"čítanie nastavení a odkazov plochy"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Povoľuje aplikácii čítať nastavenia a odkazy na ploche."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"zápis nastavení a odkazov plochy"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Povoľuje aplikácii zmeniť nastavenia a odkazy na ploche."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problém s načítaním miniaplikácií"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Toto je systémová aplikácia a nedá sa odinštalovať."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Raketomet"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Nepomenovaný priečinok"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Plocha %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Stránka %1$d z %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stránka aplikácií %1$d z %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stránka miniaplikácií %1$d z %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Vitajte!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Cíťte sa tu ako doma."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Vytvorte viac obrazoviek pre aplikácie a priečinky"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Usporiadajte svoj priestor"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Ak chcete spravovať tapetu, miniaplikácie a nastavenia, dotknite sa pozadia a podržte."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Vyberte niektoré aplikácie"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ak chcete pridať aplikáciu na plochu, dotknite sa jej a podržte."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Tu je priečinok"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ak chcete vytvoriť takýto priečinok, dotknite sa príslušnej aplikácie a podržte ju. Potom ju presuňte na druhú aplikáciu."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Otvorený priečinok, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotykom zavriete priečinok"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotykom premenovanie uložíte"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Priečinok je uzavretý"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Priečinok bol premenovaný na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Miniaplikácie"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Nastavenia"</string>
+</resources>
diff --git a/res/values-sl-land/strings.xml b/res/values-sl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sl-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..021eca8
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Zaganjalnik3"</string>
+    <string name="home" msgid="7658288663002113681">"Začetni zaslon"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Osnovne aplikacije sistema Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Nastavi ozadje"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Št. izbranih: %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Št. izbranih: %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Št. izbranih: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Ozadje %1$d od %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Izbrano: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Izbriši"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Izberi sliko"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Ozadja"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Obrezovanje ozadja"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Pripomočki"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Pripomočki"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Pokaži pomnilnik"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Za izbiro pripomočka se ga dotaknite in pridržite."</string>
+    <string name="market" msgid="2619650989819296998">"Nakup"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Elementa ni mogoče spustiti na začetni zaslon."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Izberite pripomoček za ustvarjanje"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Ime mape"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Preimenovanje mape"</string>
+    <string name="rename_action" msgid="5559600076028658757">"V redu"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Prekliči"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Dodaj na začetni zaslon"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Aplikacije"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Bližnjice"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Pripomočki"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Na začetnih zaslonih ni več prostora."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"V vrstici z ikonami ni več prostora."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ta pripomoček je prevelik za vrstico z ikonami."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« je ustvarjena."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« je bila odstranjena."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« že obstaja."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Izberite bližnjico"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Izberite aplikacijo"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Začetni zaslon"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstrani"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odstrani"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Odstrani"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odstrani"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Podatki o aplikaciji"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Iskanje"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Glasovno iskanje"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacije"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstrani"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odstrani posodobitev"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odstrani aplikacijo"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikaciji"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Izbrana je 1 aplikacija"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Izbran je 1 pripomoček"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Izbrana je 1 mapa"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Izbrana je 1 bližnjica"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"namestitev bližnjic"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odstranitev bližnjic"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Aplikaciji dovoli odstranjevanje bližnjic brez posredovanja uporabnika."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"branje nastavitev in bližnjic na začetnem zaslonu"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Aplikaciji dovoli branje nastavitev in bližnjic na začetnem zaslonu."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"zapis nastavitev in bližnjic na začetnem zaslonu"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Aplikaciji dovoli spreminjanje nastavitev in bližnjic na začetnem zaslonu."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Težava pri nalaganju pripomočka"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"To je sistemska aplikacija in je ni mogoče odstraniti."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Raketno izstrelišče"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Neimenovana mapa"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Začetni zaslon %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Stran %1$d od %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Začetni zaslon %1$d od %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stran aplikacij %1$d od %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stran pripomočkov %1$d od %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Pozdravljeni!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Počutite se kot doma."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Ustvarite več zaslonov za aplikacije in mape"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organizirajte svoj prostor"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Če želite upravljati ozadje, pripomočke in nastavitve, se dotaknite ozadja in ga pridržite."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Izberite nekaj aplikacij"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Če želite dodati aplikacijo na začetni zaslon, se je dotaknite in jo pridržite."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"To je mapa"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Če želite ustvariti mapo, podobno tej, se dotaknite aplikacije in jo pridržite, nato pa jo premaknite nad drugo."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"V redu"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mapa je odprta, <xliff:g id="WIDTH">%1$d</xliff:g> krat <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotaknite se, da zaprete mapo"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotaknite se, da shranite preimenovanje"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mapa je zaprta"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana v <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Pripomočki"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Ozadja"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Nastavitve"</string>
+</resources>
diff --git a/res/values-sr-land/strings.xml b/res/values-sr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sr-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..06d80ef
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Почетна"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Основне Android апликације"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Подеси позадину"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Изабранo je %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Изабрана je %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Изабранo je %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Позадина %1$d од %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Изабрано је <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Избриши"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Изабери слику"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Позадине"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Опсецање позадине"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Апликација није инсталирана."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Виџети"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Виџети"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Прикажи меморију"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Додирните и задржите да бисте изабрали виџет."</string>
+    <string name="market" msgid="2619650989819296998">"Купујте"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Није могуће отпустити ставку на почетни екран."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Избор виџета за прављење"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Назив директоријума"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Преименовање директоријума"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Потврди"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Откажи"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Додавање на почетни екран"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Апликације"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Пречице"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Виџети"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Нема више простора на почетним екранима."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Нема више простора на траци актуелности."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Овај виџет је превелики за траку актуелности."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ је направљена."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ је уклоњена."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ већ постоји."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Избор пречице"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Избор апликације"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Апликације"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Почетна"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Уклони"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Деинсталирај"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Уклони"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Деинсталирај"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Информације о апликацији"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Претражи"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Гласовна претрага"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Апликације"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Уклони"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Деинсталирај ажурирање"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Деинсталирање апликације"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Детаљи о апликацији"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Изабрана је 1 апликација"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Изабран је 1 виџет"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Изабран је 1 директоријум"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Изабрана је 1 пречица"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирање пречица"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозвољава апликацији да додаје пречице без интервенције корисника."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"деинсталирање пречица"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Дозвољава апликацији да уклања пречице без интервенције корисника."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"читање подешавања и пречица на почетном екрану"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Дозвољава апликацији да чита подешавања и пречице на почетном екрану."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"уписивање подешавања и пречица на почетном екрану"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Дозвољава апликацији да мења подешавања и пречице на почетном екрану."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Проблем при учитавању виџета"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ово је системска апликација и не може да се деинсталира."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Лансер ракета"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Неименовани директоријум"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Почетни екран %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. страница од %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d. почетни екран од %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d. страница апликација од %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d. страница виџета од %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Добро дошли!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Осећајте се као код куће."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Направите још екрана за апликације и директоријуме"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Организујте простор"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Додирните позадину и задржите да бисте управљали позадином, виџетима и подешавањима."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Изаберите неколико апликација"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Да бисте додали апликацију на почетни екран, додирните је и задржите."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Ево једног директоријума"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Да бисте направили директоријум попут овога, додирните и задржите апликацију, па је превуците преко друге."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Потврди"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Директоријум је отворен, <xliff:g id="WIDTH">%1$d</xliff:g> пута <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Додирните да бисте затворили директоријум"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Додирните да бисте сачували промену имена"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Директоријум је затворен"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Директоријум је преименован у <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Директоријум: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Позадине"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Подешавања"</string>
+</resources>
diff --git a/res/values-sv-land/strings.xml b/res/values-sv-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sv-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..bf75200
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Startskärm"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Ange bakgrund"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d har valts"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d har valts"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d har valts"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Bakgrund %1$d av %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> har valts"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Ta bort"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Välj bild"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Bakgrunder"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Beskär bakgrund"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widgetar"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widgetar"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Visa Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryck länge om du vill flytta en widget."</string>
+    <string name="market" msgid="2619650989819296998">"Butik"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Objektet kunde inte släppas på startskärmen."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Ange vilken widget du vill använda"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Mappnamn"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Byt namn på mapp"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Avbryt"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Lägg till på startskärmen"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Appar"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Genvägar"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widgetar"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Det finns inte plats för mer på dina startsidor."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Utrymmet på Hotseat är fullt."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denna widget är för stor för Hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Genvägen <xliff:g id="NAME">%s</xliff:g> har skapats."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Genvägen <xliff:g id="NAME">%s</xliff:g> har tagits bort."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Genvägen <xliff:g id="NAME">%s</xliff:g> finns redan."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Välj genväg"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Välj app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Appar"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskärm"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ta bort"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Avinstallera"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Ta bort"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Avinstallera"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Info om appen"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Sök"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Röstsökning"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Appar"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Ta bort"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Avinstallera uppdatering"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Avinstallera appen"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Information om appen"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"En app har valts"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"En widget har valts"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"En mapp har valts"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"En genväg har valts"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"installera genvägar"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillåter att en app lägger till genvägar utan åtgärd från användaren."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"avinstallera genvägar"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Tillåter att appen tar bort genvägar utan åtgärd från användaren."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"läsa inställningar och genvägar för startsidan"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Tillåter att appen läser inställningar och genvägar på startsidan."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"skriva inställningar och genvägar för startsidan"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Tillåter att appen ändrar inställningar och genvägar på startsidan."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Det gick inte att läsa in widgeten"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Det här är en systemapp som inte kan avinstalleras."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Namnlös mapp"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Startskärmen %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Sidan %1$d av %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskärmen %1$d av %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Appsida %1$d av %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widget-sida %1$d av %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Välkommen!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Känn dig som hemma."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Skapa fler skärmar för appar och mappar"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Organisera ditt utrymme"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Tryck länge på bakgrunden om du vill hantera bakgrundsbilder, widgetar och inställningar."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Välj några appar"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Om du vill lägga till en app på startskärmen trycker du länge på den."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Det här är en mapp"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Om du vill skapa en till mapp av det här slaget trycker du länge på en app och drar den sedan ovanpå en annan."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Mappen är öppen, <xliff:g id="WIDTH">%1$d</xliff:g> gånger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Tryck om du vill stänga mappen"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tryck om du vill spara det nya namnet"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Mappen är stängd"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgetar"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunder"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Inställningar"</string>
+</resources>
diff --git a/res/values-sw-land/strings.xml b/res/values-sw-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sw-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..606a80d
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,126 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Kizindua3"</string>
+    <string name="home" msgid="7658288663002113681">"Mwanzo"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Programu Msingi za Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Weka mandhari"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d zimechaguliwa"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d zimechaguliwa"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d zimechaguliwa"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Mandhari %1$d ya %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> iliyochaguliwa"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Futa"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Chukua picha"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Mandhari"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Punguza mandhari"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Programu haijasakinishwa."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Wijeti"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Wijeti"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Onyesha Kumbukumbu"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
+    <string name="market" msgid="2619650989819296998">"Nunua"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Haikuweza kudondosha kipengee kwenye skrini hii ya Kwanza."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Chagua wijeti ili uunde"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Jina la folda"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"lipe folda jina jipya"</string>
+    <string name="rename_action" msgid="5559600076028658757">"SAWA"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Ghairi"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Ongeza kwenye skrini ya Mwanzo"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Programu"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Njia za mkato"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Wijeti"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Hakuna nafasi zaidi kwenye skrini zako za Nyumbani."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Hakuna nafasi zaidi kwenye eneo kali."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Wijeti hii ni kubwa zaidi kwa eneo kali."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Njia ya mkato ya \"<xliff:g id="NAME">%s</xliff:g>\" imeundwa."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Njia ya mkato ya \"<xliff:g id="NAME">%s</xliff:g>\" iliondolewa."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" la njia ya mkato tayari lipo."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Chagua njia ya mkato"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Chagua programu"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Programu"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Mwanzo"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ondoa"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Ondoa"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Ondoa"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Ondoa"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Maelezo ya programu"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Tafuta"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Kutafuta kwa Kutamka"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programu"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Ondoa"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Ondoa sasisho"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Ondoa programu"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Maelezo ya programu"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Programu 1 imechaguliwa"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Wijeti 1 imechaguliwa"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Folda 1 limechaguliwa"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Njia 1 ya mkato imechaguliwa"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"sakinisha njia za mkato"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ondoa njia za mikato"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Huruhusu programu kuondoa njia za mikato bila mtumiaji kuingilia kati."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"soma mipangilio ya Mwanzo na njia za mkato"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Huruhusu programu kusoma mipangilio na njia za mikato zilizo katika skirini ya Mwanzo."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"andika mipangilio ya skrini ya Mwanzo na njia za mkato"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Huruhusu programu kubadilisha mipangilio na njia za mkato katika skrini ya Mwanzo."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Tatizo la kupakia wijeti"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Hii ni programu ya mfumo na haiwezi kuondolewa."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Kizinduzi cha Roketi"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Folda isiyo na jina"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Skrini ya mwazo %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Ukurasa%1$d wa %2$d"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+    <skip />
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ukurasa wa programu %1$d ya %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Ukurasa wa wijeti %1$d ya %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Karibu!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Jisikie huru."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Unda skrini zaidi za programu na folda"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Panga nafasi yako"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Gusa na ushikile mandharinyuma ili udhibiti mandhari, wijeti, na mipangilio."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Chagua programu kadhaa"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ili kuongeza programu kwenye Skrini yako Kuu, iguse na uishikilie."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Folda hii hapa"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ili kuunda kama hii, gusa na ushikilie programu, kisha ipitishe juu ya nyingine."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"SAWA"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folda imefunguliwa, <xliff:g id="WIDTH">%1$d</xliff:g> kwa <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Gusa ili ufunge folda"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Gusa ili uhifadhi jina jipya"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folda imefungwa"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folda imebadilishwa jina kuwa <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Wijeti"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Mandhari"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Mipangilio"</string>
+</resources>
diff --git a/res/values-sw340dp-land/dimens.xml b/res/values-sw340dp-land/dimens.xml
new file mode 100644
index 0000000..7901dc4
--- /dev/null
+++ b/res/values-sw340dp-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 201 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>
+<!-- Clings -->
+    <dimen name="folderClingMarginTop">50dp</dimen>
+</resources>
diff --git a/res/values-sw340dp-port/config.xml b/res/values-sw340dp-port/config.xml
new file mode 100644
index 0000000..d31ee59
--- /dev/null
+++ b/res/values-sw340dp-port/config.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<resources>
+<!-- Workspace -->
+    <!-- Whether or not to fade the side pages -->
+    <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+</resources>
diff --git a/res/values-sw340dp-port/dimens.xml b/res/values-sw340dp-port/dimens.xml
new file mode 100644
index 0000000..e360565
--- /dev/null
+++ b/res/values-sw340dp-port/dimens.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+<!-- Clings -->
+    <dimen name="folderClingMarginTop">70dp</dimen>
+</resources>
diff --git a/res/values-sw340dp-port/styles.xml b/res/values-sw340dp-port/styles.xml
new file mode 100644
index 0000000..24f4ba2
--- /dev/null
+++ b/res/values-sw340dp-port/styles.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<resources>
+<!-- Workspace -->
+    <style name="SearchButton">
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+    </style>
+
+<!-- AppsCustomize -->
+    <style name="TabIndicator.AppsCustomize">
+        <item name="android:maxWidth">150dp</item>
+    </style>
+</resources>
diff --git a/res/values-sw340dp/dimens.xml b/res/values-sw340dp/dimens.xml
new file mode 100644
index 0000000..69d6e58
--- /dev/null
+++ b/res/values-sw340dp/dimens.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+    <!-- Drag padding to add to the bottom of drop targets -->
+    <dimen name="drop_target_drag_padding">20dp</dimen>
+</resources>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..7f5594d
--- /dev/null
+++ b/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+<!-- AppsCustomize -->
+    <dimen name="apps_customize_pageLayoutWidthGap">36dp</dimen>
+    <dimen name="apps_customize_pageLayoutHeightGap">8dp</dimen>
+    <dimen name="apps_customize_pageLayoutPaddingTop">20dp</dimen>
+    <dimen name="apps_customize_pageLayoutPaddingBottom">14dp</dimen>
+
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">12dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">20dip</dimen>
+</resources>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..2ec2f14
--- /dev/null
+++ b/res/values-sw600dp/config.xml
@@ -0,0 +1,14 @@
+<resources>
+    <bool name="is_tablet">true</bool>
+    <bool name="allow_rotation">true</bool>
+
+    <!-- Whether or not to use custom clings if a custom workspace layout is passed in -->
+    <bool name="config_useCustomClings">true</bool>
+
+<!-- DragController -->
+    <integer name="config_flingToDeleteMinVelocity">-1000</integer>
+
+    <!-- Camera distance for the overscroll effect. We use a higher value here because the 
+         workspace screens run nearly flush to the edge of the screen-->
+    <integer name="config_cameraDistance">14000</integer>
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..8d6c7f4
--- /dev/null
+++ b/res/values-sw600dp/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+    <dimen name="app_icon_size">64dp</dimen>
+
+<!-- AppsCustomize -->
+    <dimen name="apps_customize_tab_bar_height">60dp</dimen>
+    <dimen name="apps_customize_tab_bar_margin_top">8dp</dimen>
+    <dimen name="app_widget_preview_label_margin_top">8dp</dimen>
+    <dimen name="app_widget_preview_label_margin_left">@dimen/app_widget_preview_padding_left</dimen>
+    <dimen name="app_widget_preview_label_margin_right">@dimen/app_widget_preview_padding_right</dimen>
+</resources>
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..3754304
--- /dev/null
+++ b/res/values-sw600dp/styles.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+
+<resources>
+    <style name="ClingButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">10dp</item>
+        <item name="android:paddingBottom">15dp</item>
+        <item name="android:paddingStart">35dp</item>
+        <item name="android:paddingEnd">35dp</item>
+        <item name="android:text">@string/cling_dismiss</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:background">@drawable/cling_button_bg</item>
+    </style>
+    <style name="ClingTitleText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginBottom">5dp</item>
+        <item name="android:textSize">30sp</item>
+        <item name="android:textColor">#33B5E5</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowDy">2</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+    <style name="ClingText">
+        <item name="android:textSize">22sp</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowDy">2</item>
+        <item name="android:shadowRadius">2.0</item>
+        <item name="android:lineSpacingMultiplier">1.1</item>
+    </style>
+</resources>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..eb8f83c
--- /dev/null
+++ b/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<resources>
+<!-- AppsCustomize -->
+    <integer name="apps_customize_widget_cell_count_x">4</integer>
+    <integer name="apps_customize_widget_cell_count_y">2</integer>
+    <integer name="apps_customize_cling_focused_x">4</integer>
+    <integer name="apps_customize_cling_focused_y">2</integer>
+
+<!-- Workspace -->
+    <dimen name="workspace_page_spacing">50dp</dimen>
+
+    <!-- the area at the edge of the screen that makes the workspace go left
+         or right while you're dragging. -->
+    <dimen name="scroll_zone">100dip</dimen>
+
+<!-- Cling -->
+    <!-- The offset for the text in the cling -->
+    <dimen name="cling_text_block_offset_x">140dp</dimen>
+    <dimen name="cling_text_block_offset_y">80dp</dimen>
+</resources>
diff --git a/res/values-sw720dp-port/dimens.xml b/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 0000000..62bdaaa
--- /dev/null
+++ b/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+<!-- Workspace -->
+    <!-- the area at the edge of the screen that makes the workspace go left
+         or right while you're dragging. -->
+    <dimen name="scroll_zone">40dp</dimen>
+    <dimen name="workspace_page_spacing">24dp</dimen>
+
+    <integer name="apps_customize_cling_focused_x">2</integer>
+    <integer name="apps_customize_cling_focused_y">2</integer>
+
+<!-- Cling -->
+    <!-- The offset for the text in the cling -->
+    <dimen name="cling_text_block_offset_x">80dp</dimen>
+    <dimen name="cling_text_block_offset_y">160dp</dimen>
+</resources>
diff --git a/res/values-sw720dp-port/styles.xml b/res/values-sw720dp-port/styles.xml
new file mode 100644
index 0000000..57f07ac
--- /dev/null
+++ b/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,24 @@
+<?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.
+*/
+-->
+
+<resources>
+    <style name="TabIndicator.AppsCustomize">
+        <item name="android:maxWidth">180dp</item>
+    </style>
+</resources>
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..4f537a9
--- /dev/null
+++ b/res/values-sw720dp/config.xml
@@ -0,0 +1,20 @@
+<resources>
+    <bool name="config_largeHeap">true</bool>
+    <bool name="is_large_tablet">true</bool>
+
+<!-- AllApps/Customize/AppsCustomize -->
+    <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
+    <integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
+
+<!-- Workspace -->
+    <!-- Whether or not the drop targets drop down as opposed to fade in -->
+    <bool name="config_useDropTargetDownTransition">false</bool>
+    <!-- Whether or not to fade the side pages -->
+    <bool name="config_workspaceFadeAdjacentScreens">true</bool>
+
+    <!-- Camera distance for the overscroll effect -->
+    <integer name="config_cameraDistance">18000</integer>
+
+<!-- Hotseat -->
+    <bool name="hotseat_transpose_layout_with_orientation">false</bool>
+</resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..01227e1
--- /dev/null
+++ b/res/values-sw720dp/dimens.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<resources>
+    <dimen name="app_icon_size">72dp</dimen>
+
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">8dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">8dip</dimen>
+
+    <!-- dimensions for the wallpaper picker wallpaper thumbnail width -->
+    <dimen name="wallpaper_chooser_grid_width">196dp</dimen>
+    <dimen name="wallpaper_chooser_grid_height">140dp</dimen>
+
+    <!-- When dragging items on the workspace, the number of dps by which the position of
+     the drag view should be offset from the position of the original view. -->
+    <dimen name="dragViewOffsetX">0dp</dimen>
+    <dimen name="dragViewOffsetY">0dp</dimen>
+</resources>
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
new file mode 100644
index 0000000..7269e8d
--- /dev/null
+++ b/res/values-sw720dp/styles.xml
@@ -0,0 +1,91 @@
+<?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.
+*/
+-->
+
+<resources>
+<!-- Clings -->
+    <style name="ClingButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">10dp</item>
+        <item name="android:paddingBottom">15dp</item>
+        <item name="android:paddingStart">35dp</item>
+        <item name="android:paddingEnd">35dp</item>
+        <item name="android:text">@string/cling_dismiss</item>
+        <item name="android:textSize">20sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:background">@drawable/cling_button_bg</item>
+    </style>
+    <style name="ClingTitleText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginBottom">5dp</item>
+        <item name="android:textSize">32sp</item>
+        <item name="android:textColor">#49C0EC</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowDy">2</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+    <style name="ClingText">
+        <item name="android:textSize">22sp</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowDy">2</item>
+        <item name="android:shadowRadius">2.0</item>
+        <item name="android:lineSpacingMultiplier">1.1</item>
+    </style>
+
+<!-- Workspace -->
+    <style name="Theme" parent="android:Theme.Holo.Wallpaper.NoTitleBar">
+        <item name="android:windowActionModeOverlay">true</item>
+    </style>
+
+    <style name="TabIndicator.AppsCustomize">
+        <item name="android:paddingStart">32dp</item>
+        <item name="android:paddingEnd">32dp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:maxWidth">240dp</item>
+    </style>
+
+    <!-- QSB Search / Drop Target bar -->
+    <style name="QSBBar">
+    </style>
+    <style name="SearchDropTargetBar">
+    </style>
+    <style name="SearchButton">
+    </style>
+    <style name="DropTargetButtonContainer">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+    <style name="DropTargetButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:drawablePadding">7.5dp</item>
+        <item name="android:paddingStart">60dp</item>
+        <item name="android:paddingEnd">60dp</item>
+        <item name="android:textColor">#FFFFFFFF</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:shadowColor">#393939</item>
+        <item name="android:shadowDx">0.0</item>
+        <item name="android:shadowDy">0.0</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+</resources>
diff --git a/res/values-th-land/strings.xml b/res/values-th-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-th-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..68118a7
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"หน้าแรก"</string>
+    <string name="uid_name" msgid="7820867637514617527">"แอปหลักของแอนดรอยด์"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"ตั้งค่าวอลเปเปอร์"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"เลือกไว้ %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"เลือกไว้ %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"เลือกไว้ %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"วอลเปเปอร์ %1$d จาก %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"เลือก <xliff:g id="LABEL">%1$s</xliff:g> แล้ว"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"ลบ"</string>
+    <string name="pick_image" msgid="1272073934062909527">"เลือกภาพ"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"วอลเปเปอร์"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"ครอบตัดวอลล์เปเปอร์"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"ไม่ได้ติดตั้งแอป"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"วิดเจ็ต"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"วิดเจ็ต"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"แสดง Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"แตะค้างเพื่อรับวิดเจ็ต"</string>
+    <string name="market" msgid="2619650989819296998">"เลือกซื้อ"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"ไม่สามารถวางรายการลงในหน้าจอหลักนี้"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"เลือกวิดเจ็ตที่จะสร้าง"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"ชื่อโฟลเดอร์"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"เปลี่ยนชื่อโฟลเดอร์"</string>
+    <string name="rename_action" msgid="5559600076028658757">"ตกลง"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"ยกเลิก"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"เพิ่มลงในหน้าแรก"</string>
+    <string name="group_applications" msgid="3797214114206693605">"แอป"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"ทางลัด"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"วิดเจ็ต"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"ไม่มีที่ว่างในหน้าจอหลักของคุณ"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"ไม่มีที่ว่างใน hotseat"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"วิดเจ็ตนี้มีขนาดใหญ่เกินไปสำหรับ hotseat"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"สร้างทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" แล้ว"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"นำทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" ออกแล้ว"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"มีทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" อยู่แล้ว"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"เลือกทางลัด"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"เลือกแอป"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"แอป"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"หน้าแรก"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"ลบ"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"ถอนการติดตั้ง"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"ลบ"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"ถอนการติดตั้ง"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"ข้อมูลแอป"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"ค้นหา"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ค้นหาด้วยเสียง"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"แอป"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"ลบ"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ถอนการติดตั้งการอัปเดต"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"ถอนการติดตั้งแอป"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"รายละเอียดแอป"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"เลือกไว้ 1 แอป"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"เลือกไว้ 1 วิดเจ็ต"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"เลือกไว้ 1 โฟลเดอร์"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"เลือกไว้ 1 ทางลัด"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ติดตั้งทางลัด"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ถอนการติดตั้งทางลัด"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"อนุญาตให้แอปนำทางลัดออกโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"อ่านการตั้งค่าและทางลัดหน้าแรกแล้ว"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"อนุญาตให้แอปอ่านการตั้งค่าและทางลัดในหน้าแรก"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"เขียนการตั้งค่าและทางลัดหน้าแรกแล้ว"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"อนุญาตให้แอปเปลี่ยนการตั้งค่าและทางลัดในหน้าแรก"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"มีปัญหาขณะโหลดวิดเจ็ต"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"นี่เป็นแอประบบและไม่สามารถถอนการติดตั้งได้"</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"โฟลเดอร์ที่ไม่มีชื่อ"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"หน้าจอหลัก %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"หน้า %1$d จาก %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"หน้าจอหลัก %1$d จาก %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"แอปหน้า %1$d จาก %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"วิดเจ็ตหน้า %1$d จาก %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"ยินดีต้อนรับ!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"ทำตัวตามสบาย"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"สร้างหน้าจอเพิ่มสำหรับแอปและโฟลเดอร์"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"จัดระเบียบพื้นที่ของคุณ"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"แตะพื้นหลังค้างไว้เพื่อจัดการวอลเปเปอร์ วิดเจ็ต และการตั้งค่า"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"เลือกบางแอป"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"หากต้องการเพิ่มแอปลงในหน้าจอหลัก ให้แตะแอปค้างไว้"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"นี่คือโฟลเดอร์"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"หากต้องการสร้างโฟลเดอร์ลักษณะนี้ แตะแอปค้างไว้ แล้วย้ายไปทับอีกแอปหนึ่ง"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"ตกลง"</string>
+    <string name="folder_opened" msgid="94695026776264709">"เปิดโฟลเดอร์ <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"แตะเพื่อปิดโฟลเดอร์"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"แตะเพื่อบันทึกการเปลี่ยนชื่อ"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"โฟลเดอร์ปิดอยู่"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"เปลี่ยนชื่อโฟลเดอร์เป็น <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"วิดเจ็ต"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"วอลเปเปอร์"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"การตั้งค่า"</string>
+</resources>
diff --git a/res/values-tl-land/strings.xml b/res/values-tl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-tl-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..56801b4
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Home"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Itakda ang wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d ang napili"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d ang napili"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d ang napili"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d ng %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Napili ang <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Tanggalin"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Pumili ng larawan"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Mga Wallpaper"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"I-crop ang wallpaper"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Mga Widget"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Mga Widget"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Ipakita ang Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Pindutin nang matagal upang kumuha ng widget."</string>
+    <string name="market" msgid="2619650989819296998">"Mamili"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Hindi ma-drop ang item sa Home screen na ito."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pumili ng widget na gagawin"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Pangalan ng folder"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Palitan ang pangalan ng folder"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Kanselahin"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Idagdag sa Home screen"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Mga Shortcut"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Mga Widget"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Wala nang lugar sa iyong mga Home screen."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Wala nang lugar sa hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Masyadong malaki ang widget na ito para sa hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Nagawa ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Inalis ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Umiiral na ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Pumili ng shortcut"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Pumili ng app"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Alisin"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"I-uninstall"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Alisin"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"I-uninstall"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Impormasyon ng app"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Hanapin"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Paghahanap Gamit ang Boses"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Alisin"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"I-uninstall ang update"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"I-uninstall ang app"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Mga detalye ng app"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 app ang napili"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget ang napili"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder ang napili"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut ang napili"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"i-install ang mga shortcut"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"i-uninstall ang mga shortcut"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Pinapayagan ang app na mag-alis ng mga shortcut nang walang panghihimasok ng user."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"basahin ang mga setting at shortcut ng Home"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Pinapayagan ang app na basahin ang mga setting at shortcut sa Home."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"magsulat ng mga setting at shortcut ng Home"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Pinapayagan ang app na baguhin ang mga setting at shortcut sa Home."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problema sa pag-load ng widget"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Isa itong app ng system at hindi maaaring i-uninstall."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Walang Pangalang Folder"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Pahina %1$d ng %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d ng %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pahina ng apps %1$d ng %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pahina ng widget %1$d ng %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Maligayang pagdating!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Gawing kumportable ang iyong sarili."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Gumawa ng higit pang mga screen para sa apps at mga folder"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Ayusin ang iyong espasyo"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Pindutin nang matagal ang background upang pamahalaan ang wallpaper, mga widget at setting"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Pumili ng ilang apps"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Upang magdagdag ng app sa iyong Home screen, pindutin ito nang matagal."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Narito ang isang folder"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Upang gumawa ng katulad nito, pindutin nang matagal ang isang app, pagkatapos ay ilipat ito sa isa pang folder."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Binuksan ang folder, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Pindutin upang isara ang folder"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Pindutin upang i-save ang pagpapalit ng pangalan"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Nakasara ang folder"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Pinalitan ang pangalan ng folder ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Mga Widget"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Mga Wallpaper"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Mga Setting"</string>
+</resources>
diff --git a/res/values-tr-land/strings.xml b/res/values-tr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-tr-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..dbbdee2
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Ana ekran"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android Çekirdek Uygulamaları"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Duvar kağıdını ayarla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d tane seçildi"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d tane seçildi"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d tane seçildi"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Duvar kağıdı %1$d / %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seçildi"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Sil"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Resim seç"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Duvar Kağıtları"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Duvar kağıdını kırp"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Uygulama yüklü değil."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Widget\'lar"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Widget\'lar"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Belleği Göster"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget seçmek için dokunun ve basılı tutun."</string>
+    <string name="market" msgid="2619650989819296998">"Alışveriş yap"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Öğe bu Ana ekrana bırakılamadı."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Oluşturmak için widget seçin"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Klasör adı"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Klasörü yeniden adlandırın"</string>
+    <string name="rename_action" msgid="5559600076028658757">"Tamam"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"İptal"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Ana ekrana ekleyin"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Uygulamalar"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Kısayollar"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Widget\'lar"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Ana ekranlarınızda yer kalmadı."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Favori kısa yollarda yer yok"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Bu widget, favori kısa yollar için çok büyük."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu oluşturuldu."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu kaldırıldı."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu zaten var."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Kısayolu seçin"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Uygulama seçin"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Uygulamalar"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Ana ekran"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Kaldır"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Yüklemeyi kaldır"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Kaldır"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Yüklemeyi kaldır"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Uygulama bilgileri"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Ara"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Sesli Arama"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Uygulamalar"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Kaldır"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Güncellemeyi kaldır"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uygulamanın yüklemesini kaldır"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Uygulama ayrıntıları"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 uygulama seçildi"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seçildi"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 klasör seçildi"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 kısayol seçildi"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"kısayolları yükle"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"kısayolların yüklemesini kaldır"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Uygulamaya, kullanıcı müdahalesi olmadan kısayolları kaldırma izni verir."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"Ana ekran ayarlarını ve kısayollarını oku"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Uygulamaya, Ana ekrandaki ayarları ve kısayolları okuma izni verir."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"Ana ekran ayarlarını ve kısayollarını yaz"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Uygulamaya, Ana ekrandaki ayarları ve kısayolları değiştirme izni verir."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Widget yüklenirken sorun oluştu"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Bu bir sistem uygulamasıdır ve yüklemesi kaldırılamaz."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Roket Fırlatıcı"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Adsız Klasör"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Ana ekran %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Sayfa %1$d / %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Ana ekran %1$d / %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Uygulama sayfası %1$d / %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widget sayfası %1$d / %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Hoş geldiniz!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Rahatınıza bakın."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Uygulamalar ve klasörler için daha fazla ekran oluşturun"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Alanınızı düzenleyin"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Duvar kağıdını, widget\'ları ve ayarları yönetmek için arka plana uzun basın."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"İstediğiniz uygulamaları seçin"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Bir uygulamayı Ana ekranınıza eklemek için, ilgili uygulamayı basılı tutun."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"İşte bir klasör"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Buna benzer bir klasör oluşturmak için uygulamaya uzun basın ve sonra uygulamayı başka bir uygulamanın üzerine taşıyın."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"Tamam"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Klasör açıldı, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Klasörü kapatmak için dokunun"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Yeni adı kaydetmek için dokunun"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Klasör kapatıldı"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widget\'lar"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Duvar Kağıtları"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Ayarlar"</string>
+</resources>
diff --git a/res/values-uk-land/strings.xml b/res/values-uk-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-uk-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..e0f3211
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Головний екран"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Базові програми Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Установити фон"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Вибрано %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Вибрано %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Вибрано %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Фоновий малюнок %1$d з %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"Вибрано <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Видалити"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Вибрати зображення"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Фонові малюнки"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Обрізати фоновий малюнок"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Програму не встановлено."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Віджети"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Віджети"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Показати пам’ять"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Натисніть і утримуйте, щоб вибрати віджет."</string>
+    <string name="market" msgid="2619650989819296998">"Магазин"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Не вдалося додати елемент на цей головний екран."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Вибрати віджет для створення"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Назва папки"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Перейменувати папку"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OК"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Скасувати"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Додати на головний екран"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Програми"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Ярлики"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Віджети"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"На головних екранах більше немає місця."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Немає вільного місця."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Цей віджет завеликий."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" створено."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" вилучено."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" уже існує."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Вибрати ярлик"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Вибрати програму"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Програми"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Головний екран"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Вилучити"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Видалити"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Вилучити"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Видалити"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Про програму"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Пошук"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Голосовий пошук"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Програми"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Вилучити"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Видалити оновлення"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Видалити програму"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Відомості про програму"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Вибрано 1 програму"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Вибрано 1 віджет"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Вибрано 1 папку"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Вибрано 1 ярлик"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"установлювати ярлики"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозволяє програмі самостійно додавати ярлики."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"видаляти ярлики"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Дозволяє програмі самостійно вилучати ярлики."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"читати налаштування та ярлики головного екрана"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Дозволяє програмі читати налаштування та ярлики на головному екрані."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"записувати налаштування та ярлики головного екрана"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Дозволяє програмі змінювати налаштування та ярлики на головному екрані."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Проблема із завантаженням віджета"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Це системна програма, її неможливо видалити."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Папка без назви"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Головний екран %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Сторінка %1$d з %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Головний екран %1$d з %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Сторінка програм %1$d з %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Сторінка віджетів %1$d з %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Вітаємо!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Будьте як удома."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Створюйте нові екрани для програм і папок"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Організуйте робочий простір"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Натисніть і утримуйте фон, щоб керувати фоновим малюнком, віджетами та налаштуваннями."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Виберіть програми"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Щоб додати програму на головний екран, торкніться й утримуйте її."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Це папка"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Щоб створити папку, натисніть і утримуйте програму, а потім перетягніть її на іншу."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OК"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Папку відкрито (<xliff:g id="WIDTH">%1$d</xliff:g> х <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Торкніться, щоб закрити папку"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Торкніться, щоб зберегти нову назву"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Папку закрито"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Папку перейменовано на <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Папка <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Віджети"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Фонові малюнки"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Налаштування"</string>
+</resources>
diff --git a/res/values-vi-land/strings.xml b/res/values-vi-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-vi-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..3b081d0
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"Màn hình chính"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Ứng dụng lõi Android"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Đặt hình nền"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"Đã chọn %1$d"</item>
+    <item quantity="one" msgid="142482526010824029">"Đã chọn %1$d"</item>
+    <item quantity="other" msgid="1418352074806573570">"Đã chọn %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Hình nền %1$d / %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> được chọn"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Xóa"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Chọn hình ảnh"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Hình nền"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Cắt hình nền"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Tiện ích con"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Tiện ích con"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Hiển thị bộ nhớ"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
+    <string name="market" msgid="2619650989819296998">"Mua"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Không thể thả mục vào Màn hình chính này."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Chọn tiện ích con để tạo"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Tên thư mục"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Đổi tên thư mục"</string>
+    <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Hủy"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Thêm vào Màn hình chính"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Ứng dụng"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Lối tắt"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Tiện ích con"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Không còn chỗ trên Màn hình chính của bạn."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Không còn chỗ trên vùng gắn."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Tiện ích con này quá lớn cho vùng gắn."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã được tạo."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã bị xóa."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã tồn tại."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Chọn lối tắt"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Chọn ứng dụng"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Ứng dụng"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Màn hình chính"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Xóa"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Gỡ cài đặt"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Xóa"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Gỡ cài đặt"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Thông tin ứng dụng"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Tìm kiếm"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Tìm kiếm bằng giọng nói"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Ứng dụng"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Xóa"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Gỡ cài đặt cập nhật"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Gỡ cài đặt ứng dụng"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Thông tin chi tiết về ứng dụng"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"Đã chọn 1 ứng dụng"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"Đã chọn 1 tiện ích con"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"Đã chọn 1 thư mục"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Đã chọn 1 lối tắt"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"cài đặt lối tắt"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"gỡ cài đặt lối tắt"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Cho phép ứng dụng xóa lối tắt mà không cần sự can thiệp của người dùng."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"đọc cài đặt và lối tắt trên Màn hình chính"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Cho phép ứng dụng đọc cài đặt và lối tắt trên Màn hình chính."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"ghi cài đặt và lối tắt trên Màn hình chính"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Cho phép ứng dụng thay đổi cài đặt và lối tắt trên Màn hình chính."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Sự cố khi tải tiện ích con"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Đây là ứng dụng hệ thống và không thể gỡ cài đặt."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Thư mục chưa đặt tên"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Màn hình chính %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Trang %1$d / %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Màn hình chính %1$d / %2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Trang ứng dụng %1$d / %2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Trang tiện ích con %1$d / %2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Xin chào!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Tự nhiên như ở nhà."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Tạo thêm màn hình cho ứng dụng và thư mục"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Sắp xếp không gian của bạn"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Chạm và giữ nền để quản lý hình nền, tiện ích con và cài đặt."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Chọn một số ứng dụng"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Để thêm ứng dụng vào Màn hình chính của bạn, chạm và giữ ứng dụng đó."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Đây là một thư mục"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Để tạo thư mục như thế này, hãy chạm và giữ một ứng dụng, sau đó di chuyển ứng dụng đó lên trên một ứng dụng khác."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Đã mở thư mục, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Chạm để đóng thư mục"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Chạm để lưu tên mới"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Đã đóng thư mục"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Đã đổi tên thư mục thành <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Tiện ích con"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Hình nền"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Cài đặt"</string>
+</resources>
diff --git a/res/values-zh-rCN-land/strings.xml b/res/values-zh-rCN-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zh-rCN-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..43a57c4
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"主屏"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android 核心应用"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"设置壁纸"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"已选择%1$d项"</item>
+    <item quantity="one" msgid="142482526010824029">"已选择%1$d项"</item>
+    <item quantity="other" msgid="1418352074806573570">"已选择%1$d项"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第%1$d张壁纸,共%2$d张"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"已选择<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"删除"</string>
+    <string name="pick_image" msgid="1272073934062909527">"选择图片"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"壁纸"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"剪裁壁纸"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"小部件"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"小部件"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"显示内存空间"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"触摸并按住小部件即可选择。"</string>
+    <string name="market" msgid="2619650989819296998">"商店"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"无法将相关内容拖放到此主屏幕上。"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"选择要创建的小部件"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"文件夹名称"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"重命名文件夹"</string>
+    <string name="rename_action" msgid="5559600076028658757">"确定"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"添加到主屏幕"</string>
+    <string name="group_applications" msgid="3797214114206693605">"应用"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"快捷方式"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"小部件"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"您的主屏幕上没有空间了。"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"底部区域已无空间。"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"该小部件太大,底部区域容纳不下。"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"已创建“<xliff:g id="NAME">%s</xliff:g>”快捷方式。"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"已删除“<xliff:g id="NAME">%s</xliff:g>”快捷方式。"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"“<xliff:g id="NAME">%s</xliff:g>”快捷方式已存在。"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"选择快捷方式"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"选择应用"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"应用"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"主屏幕"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"删除"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"卸载"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"删除"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"卸载"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"应用信息"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"搜索"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"语音搜索"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"应用"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"删除"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"卸载更新内容"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"卸载应用"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"应用详情"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"已选择1个应用"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"已选择1个小部件"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"已选择1个文件夹"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已选择1个快捷方式"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"安装快捷方式"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"允许应用自行添加快捷方式。"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"卸载快捷方式"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允许应用自行删除快捷方式。"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"读取主屏幕设置和快捷方式"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"允许应用读取主屏幕中的设置和快捷方式。"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"写入主屏幕设置和快捷方式"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"允许应用更改主屏幕中的设置和快捷方式。"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"加载小部件时出现问题"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"这是系统应用,无法卸载。"</string>
+    <string name="dream_name" msgid="1530253749244328964">"火箭发射器"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"未命名文件夹"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"主屏幕%1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"第%1$d页,共%2$d页"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"主屏幕:第%1$d屏,共%2$d屏"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"应用:第%1$d页,共%2$d页"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"小部件:第%1$d页,共%2$d页"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"欢迎!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"您的主屏幕您做主。"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"添加更多屏幕来容纳应用和文件夹"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"整理您的空间"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"触摸并按住背景,即可管理壁纸、小部件和设置。"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"选择一些应用"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"要将应用添加到主屏幕,请触摸并按住该应用。"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"这是一个文件夹"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"要创建一个类似的文件夹,请触摸并按住某个应用,然后将其移至另一个应用上。"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"确定"</string>
+    <string name="folder_opened" msgid="94695026776264709">"文件夹已打开,大小为<xliff:g id="WIDTH">%1$d</xliff:g>×<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"触摸可关闭文件夹"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"触摸可保存新名称"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"文件夹已关闭"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"小部件"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"设置"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..eb3def4
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"主畫面"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android 核心應用程式"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"設定桌布"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"已選取 %1$d 個"</item>
+    <item quantity="one" msgid="142482526010824029">"已選取 %1$d 個"</item>
+    <item quantity="other" msgid="1418352074806573570">"已選取 %1$d 個"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第 %1$d 張桌布,共 %2$d 張"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"已選取「<xliff:g id="LABEL">%1$s</xliff:g>」"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"刪除"</string>
+    <string name="pick_image" msgid="1272073934062909527">"選擇圖片"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"桌布"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"裁剪桌布"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"尚未安裝應用程式。"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"小工具"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"小工具"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"顯示記憶體"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
+    <string name="market" msgid="2619650989819296998">"商店"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"無法將項目拖放至主畫面。"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"選擇要建立的小工具"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"資料夾名稱"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"重新命名資料夾"</string>
+    <string name="rename_action" msgid="5559600076028658757">"確定"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"新增至主畫面"</string>
+    <string name="group_applications" msgid="3797214114206693605">"應用程式"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"捷徑"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"小工具"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"主畫面已無空間。"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"停駐區已無可用空間。"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"這個小工具過大,停駐區沒有足夠空間。"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"已建立「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"已移除「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"「<xliff:g id="NAME">%s</xliff:g>」捷徑已存在。"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"選擇捷徑"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"選擇應用程式"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"主畫面"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"移除"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"解除安裝"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"移除"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"解除安裝"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"應用程式資料"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"搜尋"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"語音搜尋"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"應用程式"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"移除"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"解除安裝更新"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"解除安裝應用程式"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"應用程式詳細資料"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"已選取 1 個應用程式"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"已選取 1 個小工具"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"已選取 1 個資料夾"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已選取 1 個捷徑"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式無需用戶許可也可新增捷徑。"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"解除安裝捷徑"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允許應用程式無需用戶許可也可移除捷徑。"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主畫面的設定和捷徑"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"允許應用程式讀取主畫面中的設定和捷徑。"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"寫入主畫面的設定和捷徑"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"允許應用程式更改主畫面中的設定和捷徑。"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"載入小工具時發生問題"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"這是系統應用程式,無法將其解除安裝。"</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"未命名的資料夾"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"主畫面 %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"主畫面 %1$d,共 %2$d 個"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"第 %1$d 個應用程式頁面,共 %2$d 頁"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"第 %1$d 個小工具頁面,共 %2$d 頁"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"歡迎!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"自訂主畫面。"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"建立更多應用程式和資料夾的畫面"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"管理您的空間"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"輕觸並按住背景,即可管理桌布、小工具和設定。"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"選擇一些應用程式"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"輕觸並按住應用程式,即可加到主畫面。"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"資料夾顯示如下"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"如要建立類似的資料夾,請輕觸並按住某個應用程式,然後疊到另一個應用程式之上。"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"確定"</string>
+    <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"輕觸即可關閉資料夾"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"輕觸即可儲存新改的名稱"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"已關閉資料夾"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"資料夾已重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-zh-rTW-land/strings.xml b/res/values-zh-rTW-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zh-rTW-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..a68f163
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+    <string name="home" msgid="7658288663002113681">"主螢幕"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Android 核心應用程式"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"設定桌布"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"已選取 %1$d 個項目"</item>
+    <item quantity="one" msgid="142482526010824029">"已選取 %1$d 個項目"</item>
+    <item quantity="other" msgid="1418352074806573570">"已選取 %1$d 個項目"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第 %1$d 張桌布,共 %2$d 張"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"已選取<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"刪除"</string>
+    <string name="pick_image" msgid="1272073934062909527">"選擇圖片"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"桌布"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"裁剪桌布"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"應用程式未安裝。"</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"小工具"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"小工具"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"顯示記憶體"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
+    <string name="market" msgid="2619650989819296998">"購物"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"無法將項目拖放至這個主螢幕上。"</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"選擇要建立的小工具"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"資料夾名稱"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"重新命名資料夾"</string>
+    <string name="rename_action" msgid="5559600076028658757">"確定"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"新增至主螢幕"</string>
+    <string name="group_applications" msgid="3797214114206693605">"應用程式"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"捷徑"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"小工具"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"主螢幕已無空間。"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"停駐區已無空間。"</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"這個小工具過大,停駐區無法容納。"</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"已建立「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"已移除「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"「<xliff:g id="NAME">%s</xliff:g>」捷徑已存在。"</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"選擇捷徑"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"選擇應用程式"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"主螢幕"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"移除"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"解除安裝"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"移除"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"解除安裝"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"應用程式資訊"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"搜尋"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"語音搜尋"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"應用程式"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"移除"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"解除安裝更新"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"解除安裝應用程式"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"應用程式詳細資料"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"已選取 1 個應用程式"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"已選取 1 個小工具"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"已選取 1 個資料夾"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已選取 1 個捷徑"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"解除安裝捷徑"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允許應用程式自動移除捷徑。"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主螢幕的設定和捷徑"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"允許應用程式讀取主螢幕中的設定和捷徑。"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"寫入主螢幕設定和捷徑"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"允許應用程式變更主螢幕中的設定和捷徑。"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"載入小工具時發生問題"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"這是系統應用程式,不可解除安裝。"</string>
+    <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"未命名的資料夾"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"主螢幕 %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"主螢幕:第 %1$d 頁,共 %2$d 頁"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"應用程式:第 %1$d 頁,共 %2$d 頁"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"小工具:第 %1$d 頁,共 %2$d 頁"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"歡迎使用!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"主螢幕由您作主。"</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"建立更多畫面容納應用程式和資料夾"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"管理您的空間"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"輕觸並按住背景,即可管理桌布、小工具和設定。"</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"選擇一些應用程式"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"如要將應用程式新增至主螢幕,請輕觸並按住目標。"</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"資料夾顯示如下"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"如要建立類似的資料夾,請輕觸並按住應用程式,然後將應用程式疊放在另一個應用程式上。"</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"確定"</string>
+    <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"輕觸即可關閉資料夾"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"輕觸即可儲存新名稱"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"資料夾已關閉"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"已將資料夾重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-zu-land/strings.xml b/res/values-zu-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zu-land/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="delete_target_label" msgid="4155210680095864979"></string>
+    <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+    <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..da790e6
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,124 @@
+<?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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name" msgid="5181331383435256801">"Isiqalisi3"</string>
+    <string name="home" msgid="7658288663002113681">"Ikhaya"</string>
+    <string name="uid_name" msgid="7820867637514617527">"Izinhlelo zokusebenza ze-Android Core"</string>
+    <string name="folder_name" msgid="7371454440695724752"></string>
+    <string name="wallpaper_instructions" msgid="563973358787555519">"Setha isithombe sangemuva"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="7464587177007785408">"%1$d khethiwe"</item>
+    <item quantity="one" msgid="142482526010824029">"%1$d khethiwe"</item>
+    <item quantity="other" msgid="1418352074806573570">"%1$d khethiwe"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Isithombe sangemuva se-%1$d of %2$d"</string>
+    <string name="announce_selection" msgid="8338254712932127413">"I-<xliff:g id="LABEL">%1$s</xliff:g> ekhethiwe"</string>
+    <string name="wallpaper_delete" msgid="8095005658756613921">"Susa"</string>
+    <string name="pick_image" msgid="1272073934062909527">"Thatha isithombe"</string>
+    <string name="pick_wallpaper" msgid="8179698221502010609">"Izithombe zangemuva"</string>
+    <string name="crop_wallpaper" msgid="8334345984491368009">"Nqampuna isithombe sangemuva"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"Uhlelo lokusebenza alufakiwe."</string>
+    <string name="widgets_tab_label" msgid="2921133187116603919">"Amawijethi"</string>
+    <string name="widget_adder" msgid="3201040140710381657">"Amawijethi"</string>
+    <string name="toggle_weight_watcher" msgid="5645299835184636119">"Bonisa i-Mem"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Thinta uphinde ubambe ukuze uphakamise iwijethi."</string>
+    <string name="market" msgid="2619650989819296998">"Thenga"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="external_drop_widget_error" msgid="3165821058322217155">"Ayikwazanga ukwehlisela into kulesi sikrini se-Ikhaya."</string>
+    <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Khetha iwijethi ongayidala"</string>
+    <string name="rename_folder_label" msgid="3727762225964550653">"Igama lefolda"</string>
+    <string name="rename_folder_title" msgid="3771389277707820891">"Qamba kabusha ifolda"</string>
+    <string name="rename_action" msgid="5559600076028658757">"KULUNGILE"</string>
+    <string name="cancel_action" msgid="7009134900002915310">"Khansela"</string>
+    <string name="menu_item_add_item" msgid="1264911265836810421">"Faka kwisikrini saseKhaya"</string>
+    <string name="group_applications" msgid="3797214114206693605">"Izinhlelo zokusebenza"</string>
+    <string name="group_shortcuts" msgid="6012256992764410535">"Izinqamuleli"</string>
+    <string name="group_widgets" msgid="1569030723286851002">"Amawijethi"</string>
+    <string name="completely_out_of_space" msgid="6106288382070760318">"Akusenagumbi ezikrinini zakho Zekhaya."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
+    <string name="hotseat_out_of_space" msgid="9139760413395605841">"Akusenagumbi ku-hotseat."</string>
+    <string name="invalid_hotseat_item" msgid="1211534262129849507">"Le wijethi inkulu kakhulu ukuba ku-hotseat."</string>
+    <string name="shortcut_installed" msgid="1701742129426969556">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sidaliwe."</string>
+    <string name="shortcut_uninstalled" msgid="8176767991305701821">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sisusiwe."</string>
+    <string name="shortcut_duplicate" msgid="9167217446062498127">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sesivele sikhona."</string>
+    <string name="title_select_shortcut" msgid="6680642571148153868">"Khetha isinqamulelo"</string>
+    <string name="title_select_application" msgid="3280812711670683644">"Khetha uhlelo lokusebenza"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"Izinhlelo zokusebenza"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Ikhaya"</string>
+    <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Susa"</string>
+    <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Khipha"</string>
+    <string name="delete_target_label" msgid="1822697352535677073">"Susa"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Khipha"</string>
+    <string name="info_target_label" msgid="8053346143994679532">"Ulwazi lohlelo lokusebenza"</string>
+    <string name="accessibility_search_button" msgid="1628520399424565142">"Sesha"</string>
+    <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ukusesha ngezwi"</string>
+    <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Izinhlelo zokusebenza"</string>
+    <string name="accessibility_delete_button" msgid="6466114477993744621">"Susa"</string>
+    <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Khipha isibuyekezo"</string>
+    <string name="cab_menu_delete_app" msgid="7435191475867183689">"Khipha uhlelo lokusebenza"</string>
+    <string name="cab_menu_app_info" msgid="8593722221450362342">"Imininingwane yohlelo lokusebenza"</string>
+    <string name="cab_app_selection_text" msgid="374688303047985416">"1 uhlelo lokusebenza olukhethiwe"</string>
+    <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 iwijethi ekhethiwe"</string>
+    <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ifolda ekhethiwe"</string>
+    <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 isinqamuleli esikhethiwe"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"faka izinqamuleli"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi."</string>
+    <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"khipha izinqamuleli"</string>
+    <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ivumela uhlelo lokusebenza ukuthi lisuse izinqamuleli ngaphandle kokungenelela komsebenzisi."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"funda izilungiselelo zokuthi Ikhaya nezinqamuleli"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Ivumela uhlelo lokusebenza ukuthi lifunde izilungiselelo nezinqamuleli ekhaya."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"bhala izilungiselelo zokuthi Ikhaya nezinqamuleli"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Ivumela uhlelo lokusebenza ukuthi lushintshe izilungiselelo nezinqamuleli Ekhaya."</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Inkinga yokulayisha iwijethi"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"Lolu uhlelo lokusebenza lwesistimu futhi alikwazi ukukhishwa."</string>
+    <string name="dream_name" msgid="1530253749244328964">"Isiqalisi se-Rocket"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Ifolda engenagama"</string>
+    <string name="workspace_description_format" msgid="2950174241104043327">"Isikrini sasekhaya esingu-%1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Ikhasi elingu-%1$d kwangu-%2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Isikrini sasekhaya esingu-%1$d se-%2$d"</string>
+    <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ikhasi lezinhlelo zokusebenza elingu-%1$d le-%2$d"</string>
+    <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Ikhasi lamawijethi elingu-%1$d le-%2$d"</string>
+    <string name="first_run_cling_title" msgid="7257389003637362144">"Siyakwamukela!"</string>
+    <string name="first_run_cling_description" msgid="6447072552696253358">"Zizwe usekhaya."</string>
+    <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+    <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+    <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Dala izikrini eziningi zezinhlelo zokusebenza namafolda"</string>
+    <string name="workspace_cling_title" msgid="5626202359865825661">"Hlela isikhala sakho"</string>
+    <string name="workspace_cling_move_item" msgid="528201129978005352">"Thinta uphinde ubambe okungemuva ukuze uphathe isithombe sangemuva, amawijethi nezilungiselelo."</string>
+    <string name="all_apps_cling_title" msgid="34929250753095858">"Khetha izinhlelo zokusebenza ezithile"</string>
+    <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ukuze ungeze uhlelo lokusebenza kusikrini sakho se-Ikhaya, thinta futhi uyibambe."</string>
+    <string name="folder_cling_title" msgid="3894908818693254164">"Nayi ifolda"</string>
+    <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ukuze udale eyodwa efana nale, thinta uphinde ubambe uhlelo lokusebenza, bese ulidlulisa ngaphezulu kwelinye."</string>
+    <string name="cling_dismiss" msgid="8962359497601507581">"KULUNGILE"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Ifolda ivuliwe, <xliff:g id="WIDTH">%1$d</xliff:g> nge-<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="1884479294466410023">"Thinta ukuze uvale ifolda"</string>
+    <string name="folder_tap_to_rename" msgid="9191075570492871147">"Thinta ukuze ulondoloze ukuqamba kabusha"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Ifolda ivaliwe"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Ifolda iqanjwe kabusha ngo-<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+    <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+    <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+    <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Amawijethi"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Izithombe zangemuva"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Izilungiselelo"</string>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..a2d3a83
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+
+<resources>
+
+    <!-- DrawableStateProxyView specific attributes. These attributes are used to customize
+         a DrawableStateProxyView view in XML files. -->
+    <declare-styleable name="DrawableStateProxyView">
+        <!-- The source view to delegate touch presses events to. -->
+        <attr name="sourceViewId" format="integer" />
+    </declare-styleable>
+
+    <!-- Cling specific attributes. These attributes are used to customize
+         the cling in XML files. -->
+    <declare-styleable name="Cling">
+        <!-- Used to identify how to draw the cling bg -->
+        <attr name="drawIdentifier" format="string"  />
+    </declare-styleable>
+
+    <!-- Page Indicator specific attributes. These attributes are used to customize
+         the cling in XML files. -->
+    <declare-styleable name="PageIndicator">
+        <!-- Used to identify how to draw the cling bg -->
+        <attr name="windowSize" format="integer"  />
+    </declare-styleable>
+
+    <!-- Workspace specific attributes. These attributes are used to customize
+         the workspace in XML files. -->
+    <declare-styleable name="Workspace">
+        <!-- The first screen the workspace should display. -->
+        <attr name="defaultScreen" format="integer"  />
+        <!-- The number of horizontal cells in the CellLayout -->
+        <attr name="cellCountX" format="integer"  />
+        <!-- The number of vertical cells in the CellLayout -->
+        <attr name="cellCountY" format="integer"  />
+    </declare-styleable>
+    
+    <!-- Hotseat specific attributes. These attributes are used to customize
+         the hotseat in XML files. -->
+    <declare-styleable name="Hotseat">
+        <!-- The number of horizontal cells in the CellLayout -->
+        <attr name="cellCountX" />
+        <!-- The number of vertical cells in the CellLayout -->
+        <attr name="cellCountY" />
+    </declare-styleable>
+
+    <!-- CellLayout specific attributes. These attributes are used to customize
+         a CellLayout view in XML files. -->
+    <declare-styleable name="CellLayout">
+        <!-- The width of a single cell -->
+        <attr name="cellWidth" format="dimension"  />
+        <!-- The height of a single cell -->
+        <attr name="cellHeight" format="dimension"  />
+        <!-- An override for the width and height gap to allow users to specify
+             a specific size for the page using spacing instead of resolving the
+             spacing from the width of the page -->
+        <attr name="widthGap" format="dimension" />
+        <attr name="heightGap" format="dimension" />
+        <!-- The max gap size for each dimension -->
+        <attr name="maxGap" format="dimension" />
+    </declare-styleable>
+
+    <!-- StrokedTextView specific attributes. -->
+    <declare-styleable name="StrokedTextView">
+        <!-- The color of the stroke outline -->
+        <attr name="strokeColor" format="color" />
+        <!-- The color of the text -->
+        <attr name="strokeTextColor" format="color" />
+        <!-- The width of the stroke -->
+        <attr name="strokeWidth" format="float" />
+    </declare-styleable>
+
+    <!-- HolographicLinearLayout specific attributes. -->
+    <declare-styleable name="HolographicLinearLayout">
+        <!-- The source view to generate and apply the drawable states to/from -->
+        <attr name="sourceImageViewId" format="integer" />
+        <attr name="stateHotwordOn" format="boolean" />
+    </declare-styleable>
+
+    <!-- PagedView specific attributes. These attributes are used to customize
+         a PagedView view in XML files. -->
+    <declare-styleable name="PagedView">
+        <!-- A spacing override for the icons within a page -->
+        <attr name="pageLayoutWidthGap" format="dimension" />
+        <attr name="pageLayoutHeightGap" format="dimension" />
+        <!-- The padding of the pages that are dynamically created per page -->
+        <attr name="pageLayoutPaddingTop" format="dimension" />
+        <attr name="pageLayoutPaddingBottom" format="dimension" />
+        <attr name="pageLayoutPaddingLeft" format="dimension" />
+        <attr name="pageLayoutPaddingRight" format="dimension" />
+        <!-- The space between adjacent pages of the PagedView. -->
+        <attr name="pageSpacing" format="dimension" />
+        <!-- The page indicator for this workspace -->
+        <attr name="pageIndicator" format="reference" />
+    </declare-styleable>
+
+    <!-- AppsCustomizePagedView specific attributes.  These attributes are used to
+         customize an AppsCustomizePagedView in xml files. -->
+    <declare-styleable name="AppsCustomizePagedView">
+        <!-- Max number of cells of applications horizontally -->
+        <attr name="maxAppCellCountX" format="integer" />
+        <!-- Max number of cells of applications vertically -->
+        <attr name="maxAppCellCountY" format="integer" />
+        <!-- Horizontal spacing between widgets and wallpapers -->
+        <attr name="widgetCellWidthGap" format="dimension" />
+        <!-- Vertical spacing between widgets -->
+        <attr name="widgetCellHeightGap" format="dimension" />
+        <!-- Number of widgets horizontally -->
+        <attr name="widgetCountX" format="integer" />
+        <!-- Number of widgets vertically -->
+        <attr name="widgetCountY" format="integer" />
+        <!-- The x index of the item to be focused in the cling -->
+        <attr name="clingFocusedX" format="integer" />
+        <!-- The y index of the item to be focused in the cling -->
+        <attr name="clingFocusedY" format="integer" />
+    </declare-styleable>
+
+    <!-- XML attributes used by default_workspace.xml -->
+    <declare-styleable name="Favorite">
+        <attr name="className" format="string" />
+        <attr name="packageName" format="string" />
+        <attr name="container" format="string" />
+        <attr name="screen" format="string" />
+        <attr name="x" format="string" />
+        <attr name="y" format="string" />
+        <attr name="spanX" format="string" />
+        <attr name="spanY" format="string" />
+        <attr name="icon" format="reference" />  
+        <attr name="title" format="reference" />
+        <attr name="uri" format="string" />
+    </declare-styleable>
+    <declare-styleable name="Extra">
+        <attr name="key" format="string" />
+        <attr name="value" format="string" />
+    </declare-styleable>
+    <declare-styleable name="Include">
+        <attr name="workspace" format="reference" />
+    </declare-styleable>
+
+    <!-- Only used in the device overlays -->
+    <declare-styleable name="CustomClingTitleText">
+    </declare-styleable>
+    <declare-styleable name="CustomClingText">
+    </declare-styleable>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..dc35a3f
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <!-- The color tints to apply to the text and drag view when hovering
+         over the delete target or the info target -->
+    <color name="delete_target_hover_tint">#DAFF0000</color>
+    <color name="info_target_hover_tint">#DA0099CC</color>
+
+    <color name="bubble_dark_background">#20000000</color>
+
+    <color name="appwidget_error_color">#FCCC</color>
+
+    <color name="workspace_all_apps_and_delete_zone_text_color">#CCFFFFFF</color>
+    <color name="workspace_all_apps_and_delete_zone_text_shadow_color">#A0000000</color>
+    <color name="workspace_icon_text_color">#FFF</color>
+
+    <color name="apps_customize_icon_text_color">#FFF</color>
+    <color name="wallpaper_picker_translucent_gray">#66000000</color>
+    <color name="folder_items_text_color">#FF333333</color>
+    <color name="outline_color">#FFFFFFFF</color>
+    
+    <color name="first_run_cling_circle_background_color">#64b1ea</color>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..4978281
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,91 @@
+<resources>
+    <bool name="config_largeHeap">false</bool>
+    <bool name="is_tablet">false</bool>
+    <bool name="is_large_tablet">false</bool>
+    <bool name="allow_rotation">false</bool>
+
+    <!-- Max number of page indicators to show -->
+    <integer name="config_maxNumberOfPageIndicatorsToShow">21</integer>
+
+<!-- DragController -->
+    <integer name="config_flingToDeleteMinVelocity">-1500</integer>
+
+<!-- AllApps/Customize/AppsCustomize -->
+    <!-- The alpha of the AppsCustomize bg in spring loaded mode -->
+    <integer name="config_appsCustomizeSpringLoadedBgAlpha">65</integer>
+    <integer name="config_workspaceUnshrinkTime">300</integer>
+    <integer name="config_overviewTransitionTime">250</integer>
+
+    <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
+    <integer name="config_workspaceSpringLoadShrinkPercentage">80</integer>
+    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
+    <integer name="config_workspaceOverviewShrinkPercentage">58</integer>
+
+    <!-- Fade/zoom in/out duration & scale in the AllApps transition.
+         Note: This should be less than the workspaceShrinkTime as they happen together. -->
+    <integer name="config_appsCustomizeZoomInTime">350</integer>
+    <integer name="config_appsCustomizeZoomOutTime">600</integer>
+    <integer name="config_appsCustomizeZoomScaleFactor">7</integer>
+    <integer name="config_appsCustomizeFadeInTime">250</integer>
+    <integer name="config_appsCustomizeFadeOutTime">200</integer>
+    <integer name="config_appsCustomizeWorkspaceShrinkTime">300</integer>
+    <integer name="config_appsCustomizeWorkspaceAnimationStagger">40</integer>
+    <integer name="config_workspaceAppsCustomizeAnimationStagger">100</integer>
+
+    <integer name="config_workspaceDefaultScreen">0</integer>
+
+    <!-- Tab transition animation duration -->
+    <integer name="config_tabTransitionDuration">250</integer>
+
+    <!-- The slope, in percent, of the drag movement needed to drag an item out of
+         AppsCustomize (y / x * 100%)  -->
+    <integer name="config_appsCustomizeDragSlopeThreshold">150</integer>
+
+<!-- Workspace -->
+    <!-- Whether or not the drop targets drop down as opposed to fade in -->
+    <bool name="config_useDropTargetDownTransition">false</bool>
+    <!-- Whether or not to fade the side pages -->
+    <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+
+    <!-- The transition duration for the background of the drop targets -->
+    <integer name="config_dropTargetBgTransitionDuration">0</integer>
+
+    <!-- The duration (in ms) of the fade animation on the object outlines, used when
+         we are dragging objects around on the home screen. -->
+    <integer name="config_dragOutlineFadeTime">900</integer>
+
+    <!-- The alpha value at which to show the most recent drop visualization outline. -->
+    <integer name="config_dragOutlineMaxAlpha">128</integer>
+
+    <!-- Parameters controlling the animation for when an item is dropped on the home screen,
+         and it animates from its old position to the new one. -->
+    <integer name="config_dropAnimMinDuration">100</integer>
+    <integer name="config_dropAnimMaxDuration">500</integer>
+
+    <!-- The duration of the UserFolder opening and closing animation -->
+    <integer name="config_folderAnimDuration">120</integer>
+
+    <!-- The distance at which the animation should take the max duration -->
+    <integer name="config_dropAnimMaxDist">800</integer>
+
+    <!-- Properties controlling the workspace fade-out during dragging -->
+    <integer name="config_dragFadeOutAlpha">80</integer>
+    <integer name="config_dragFadeOutDuration">250</integer>
+
+    <!-- Camera distance for the overscroll effect -->
+    <integer name="config_cameraDistance">8000</integer>
+
+    <!-- Whether or not to use custom clings if a custom workspace layout is passed in -->
+    <bool name="config_useCustomClings">false</bool>
+
+<!-- Hotseat -->
+    <bool name="hotseat_transpose_layout_with_orientation">true</bool>
+
+    <!-- Memory debugging, including a memory dump icon -->
+    <bool name="debug_memory_enabled">false</bool>
+
+    <!-- Name of a subclass of com.android.launcher3.AppFilter used to
+         filter the activities shown in the launcher. Can be empty. -->
+    <string name="app_filter_class" translatable="false"></string>
+
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..b4b2367
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+<!-- Dynamic Grid -->
+    <dimen name="dynamic_grid_edge_margin">6dp</dimen>
+    <dimen name="dynamic_grid_search_bar_max_width">500dp</dimen>
+    <dimen name="dynamic_grid_search_bar_height">48dp</dimen>
+    <dimen name="dynamic_grid_page_indicator_height">24dp</dimen>
+
+<!-- Wallpaper picker -->
+    <dimen name="wallpaperThumbnailWidth">106.5dp</dimen>
+    <dimen name="wallpaperThumbnailHeight">94.5dp</dimen>
+    <dimen name="wallpaperItemIconSize">32dp</dimen>
+
+<!-- Cling -->
+    <dimen name="clingPunchThroughGraphicCenterRadius">94dp</dimen>
+    <dimen name="folderClingMarginTop">20dp</dimen>
+    <!-- The offset for the text in the cling -->
+    <dimen name="cling_text_block_offset_x">0dp</dimen>
+    <dimen name="cling_text_block_offset_y">0dp</dimen>
+    <!-- entries for custom clings, will be set in overlays -->
+    <add-resource type="dimen" name="custom_cling_margin_top" />
+    <add-resource type="dimen" name="custom_cling_margin_right" />
+    <add-resource type="dimen" name="custom_cling_margin_left" />
+
+<!-- Workspace -->
+    <dimen name="workspace_max_gap">16dp</dimen>
+    <dimen name="workspace_overscroll_drawable_padding">0dp</dimen>
+    <dimen name="workspace_spring_loaded_page_spacing">15dp</dimen>
+    <dimen name="overview_panel_bottom_padding">50dp</dimen>
+    <dimen name="overview_panel_buttonSpacing">60dp</dimen>
+    <dimen name="overview_mode_page_offset">130dp</dimen>
+
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">4dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">12dip</dimen>
+    <!-- External toolbar icon size (for bounds) -->
+    <dimen name="toolbar_external_icon_width">36dp</dimen>
+    <dimen name="toolbar_external_icon_height">36dp</dimen>
+
+<!-- AllApps/Customize/AppsCustomize -->
+    <!-- The height of the tab bar - if this changes, we should update the
+         external icon width/height above to compensate -->
+    <dimen name="apps_customize_tab_bar_height">52dp</dimen>
+    <dimen name="apps_customize_tab_bar_margin_top">0dp</dimen>
+    <dimen name="app_icon_size">48dp</dimen>
+
+    <!-- The AppsCustomize page indicator -->
+    <dimen name="apps_customize_page_indicator_height">12dp</dimen>
+    <dimen name="apps_customize_page_indicator_margin">4dp</dimen>
+    <dimen name="apps_customize_page_indicator_offset">16dp</dimen>
+
+    <!-- Drag padding to add to the bottom of drop targets -->
+    <dimen name="drop_target_drag_padding">14dp</dimen>
+
+<!-- Dragging -->
+    <!-- the area at the edge of the screen that makes the workspace go left
+         or right while you're dragging. -->
+    <dimen name="scroll_zone">20dp</dimen>
+
+    <!-- When dragging items on the workspace, the number of dps by which the position of
+     the drag view should be offset from the position of the original view. -->
+    <dimen name="dragViewOffsetX">0dp</dimen>
+    <dimen name="dragViewOffsetY">0dp</dimen>
+    <!--  When dragging an item, how much bigger (fixed dps) the dragged view
+          should be. If 0, it will not be scaled at all. -->
+    <dimen name="dragViewScale">12dp</dimen>
+
+    <!-- Padding applied to AppWidget previews -->
+    <dimen name="app_widget_preview_padding_left">16dp</dimen>
+    <dimen name="app_widget_preview_padding_right">16dp</dimen>
+    <dimen name="app_widget_preview_padding_top">32dp</dimen>
+    <dimen name="app_widget_preview_label_margin_top">4dp</dimen>
+    <dimen name="app_widget_preview_label_margin_left">2dp</dimen>
+    <dimen name="app_widget_preview_label_margin_right">2dp</dimen>
+
+    <!-- Padding applied to shortcut previews -->
+    <dimen name="shortcut_preview_padding_left">0dp</dimen>
+    <dimen name="shortcut_preview_padding_right">0dp</dimen>
+    <dimen name="shortcut_preview_padding_top">0dp</dimen>
+
+<!-- Folders -->
+    <!-- The amount that the preview contents are inset from the preview background -->
+    <dimen name="folder_preview_padding">4dp</dimen>
+    <dimen name="folder_name_padding">10dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..cafa424
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,297 @@
+<?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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- General -->
+    <skip />
+    <!-- Application name -->
+    <string name="application_name">Launcher3</string>
+    <!-- Accessibility-facing application name -->
+    <string name="home">Home</string>
+    <!-- Name for all applications running as this uid. -->
+    <string name="uid_name">Android Core Apps</string>
+    <!-- Default folder name -->
+    <string name="folder_name"></string>
+    <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
+    <string name="wallpaper_instructions">Set 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">Pick image</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>
+    <!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
+    <string name="activity_not_found">App isn\'t installed.</string>
+    <!--  Labels for the tabs in the customize drawer -->
+    <string name="widgets_tab_label">Widgets</string>
+
+    <string name="widget_adder">Widgets</string>
+
+    <string name="toggle_weight_watcher">Show Mem</string>
+
+    <!-- AppsCustomize pane -->
+    <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
+    <string name="long_press_widget_to_add">Touch &amp; hold to pick up a widget.</string>
+    <!-- Market button text.  The market button text is removed in Launcher.java 
+         in the Phone UI. [CHAR LIMIT=32] -->
+    <string name="market">Shop</string>
+    <!-- The format string for the dimensions of a widget in the drawer -->
+    <!-- There is a special version of this format string for Farsi -->
+    <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
+
+    <!-- External-drop widget pick label format string [CHAR_LIMIT=25] -->
+    <string name="external_drop_widget_pick_format" translatable="false">%1$s (%2$d \u00d7 %3$d)</string>
+    <!-- External-drop widget error string.  This is the error that is shown
+         when you drag and item into the homescreen and it is unable to fit,
+         or an error is encountered. [CHAR_LIMIT=50] -->
+    <string name="external_drop_widget_error">Couldn\'t drop item on this Home screen.</string>
+    <!-- External-drop widget pick title.  This is shown as the title of the
+         dialog which allows you to pick which widgets to handle a particular
+         drop if there are multiple choices. [CHAR_LIMIT=35] -->
+    <string name="external_drop_widget_pick_title">Choose widget to create</string>
+
+    <!-- Folders -->
+    <skip />
+    <!-- Label of Folder name field in Rename folder dialog box -->
+    <string name="rename_folder_label">Folder name</string>
+    <!-- Title of dialog box -->
+    <string name="rename_folder_title">Rename folder</string>
+    <!-- Buttons in Rename folder dialog box -->
+    <string name="rename_action">OK</string>
+    <!-- Buttons in Rename folder dialog box -->
+    <string name="cancel_action">Cancel</string>
+
+    <!-- Shortcuts -->
+    <skip />
+    <!-- Title of dialog box -->
+    <string name="menu_item_add_item">Add to Home screen</string>
+    <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all apps -->
+    <string name="group_applications">Apps</string>
+    <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all shortcut
+s -->
+    <string name="group_shortcuts">Shortcuts</string>
+    <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all widgets/gadgets -->
+    <string name="group_widgets">Widgets</string>
+    <!-- Error message when user has filled all their home screens -->
+    <string name="completely_out_of_space">No more room on your Home screens.</string>
+    <!-- Error message when user has filled a home screen -->
+    <string name="out_of_space">No more room on this Home screen.</string>
+    <!-- Error message when user has filled the hotseat -->
+    <string name="hotseat_out_of_space">No more room on the hotseat.</string>
+    <!-- Error message when user tries to drop an invalid item on the hotseat -->
+    <string name="invalid_hotseat_item">This widget is too large for the hotseat.</string>
+    <!-- Message displayed when a shortcut is created by an external application -->
+    <string name="shortcut_installed">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" created.</string>
+    <!-- Message displayed when a shortcut is uninstalled by an external application -->
+    <string name="shortcut_uninstalled">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" was removed.</string>
+    <!-- Message displayed when an external application attemps to create a shortcut that already exists -->
+    <string name="shortcut_duplicate">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" already exists.</string>
+
+    <!-- Title of dialog when user is selecting shortcut to add to homescreen -->
+    <string name="title_select_shortcut">Choose shortcut</string>
+    <!-- Title of dialog when user is selecting an application to add to homescreen -->
+    <string name="title_select_application">Choose app</string>
+
+    <!-- All applications label -->
+    <string name="all_apps_button_label">Apps</string>
+    <!-- Label for button in all applications label to go back home (to the workspace / desktop)
+         for accessibilty (spoken when the button gets focus). -->
+    <string name="all_apps_home_button_label">Home</string>
+    <!-- Label for trash icon on workspace. Meant to communicate the idea of removing the
+         icon/widget from the home screen, but not permanently. [CHAR_LIMIT=30] -->
+    <string name="delete_zone_label_workspace">Remove</string>
+    <!-- Label for trash icon in All Apps. The icon/widget will become completely unavailable on the
+         device. [CHAR_LIMIT=30]-->
+    <string name="delete_zone_label_all_apps">Uninstall</string>
+
+    <!-- Label for delete drop target. [CHAR_LIMIT=30] -->
+    <string name="delete_target_label">Remove</string>
+    <!-- Label for uninstall drop target. [CHAR_LIMIT=30]-->
+    <string name="delete_target_uninstall_label">Uninstall</string>
+    <!-- Label for the info icon. [CHAR_LIMIT=30] -->
+    <string name="info_target_label">App info</string>
+
+    <!-- Accessibility: Search button -->
+    <string name="accessibility_search_button">Search</string>
+    <!-- Accessibility: Voice Search button -->
+    <string name="accessibility_voice_search_button">Voice Search</string>
+    <!-- Accessibility: AllApps button -->
+    <string name="accessibility_all_apps_button">Apps</string>
+    <!-- Accessibility: Delete button -->
+    <string name="accessibility_delete_button">Remove</string>
+
+    <!-- Label for trash icon in All Apps, when an updated system app is selected. The update will
+         be uninstalled. [CHAR_LIMIT=30] -->
+    <string name="delete_zone_label_all_apps_system_app">Uninstall update</string>
+
+    <!-- URL pointing to help text. If empty, no link to help will be created [DO NOT TRANSLATE] -->
+    <string name="help_url" translatable="false"></string>
+
+    <!--  Strings for the contextual action bar (CAB) in All Apps -->
+    <skip />
+    <!-- Describes the button for uninstalling the currently selected application.
+         Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] -->
+    <string name="cab_menu_delete_app">Uninstall app</string>
+    <!-- Describes the button for getting details/info about currently selected application.
+         Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] -->
+    <string name="cab_menu_app_info">App details</string>
+    <!-- Appears in the CAB when an app is selected in All Apps or Customize mode. [CHAR_LIMIT=50] -->
+    <string name="cab_app_selection_text">1 app selected</string>
+    <!-- Appears in the CAB when a widget is selected in Customize mode. [CHAR_LIMIT=50] -->
+    <string name="cab_widget_selection_text">1 widget selected</string>
+    <!-- Appears in the CAB when a folder is selected in Customize mode. [CHAR_LIMIT=50] -->
+    <string name="cab_folder_selection_text">1 folder selected</string>
+    <!-- Appears in the CAB when a shortcut is selected in Customize mode. [CHAR_LIMIT=50] -->
+    <string name="cab_shortcut_selection_text">1 shortcut selected</string>
+
+    <!-- Permissions: -->
+    <skip />
+    <!-- Permission short label -->
+    <string name="permlab_install_shortcut">install shortcuts</string>
+    <!-- Permission description -->
+    <string name="permdesc_install_shortcut">Allows an app to add
+        shortcuts without user intervention.</string>
+    <!-- Permission short label -->
+    <string name="permlab_uninstall_shortcut">uninstall shortcuts</string>
+    <!-- Permission description -->
+    <string name="permdesc_uninstall_shortcut">Allows the app to remove
+        shortcuts without user intervention.</string>
+    <!-- Permission short label -->
+    <string name="permlab_read_settings">read Home settings and shortcuts</string>
+    <!-- Permission description -->
+    <string name="permdesc_read_settings">Allows the app to read the settings and
+        shortcuts in Home.</string>
+    <!-- Permission short label -->
+    <string name="permlab_write_settings">write Home settings and shortcuts</string>
+    <!-- Permission description -->
+    <string name="permdesc_write_settings">Allows the app to change the settings and
+        shortcuts in Home.</string>
+
+    <!-- Widgets: -->
+    <skip />    
+
+    <!-- Text to show user in place of a gadget when we can't display it properly -->
+    <string name="gadget_error_text">Problem loading widget</string>
+
+    <!-- Text to inform the user that they can't uninstall a system application -->
+    <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
+
+    <!-- Title of the Android Dreams (screensaver) module -->
+    <string name="dream_name">Rocket Launcher</string>
+
+    <!-- Default folder title -->
+    <string name="folder_hint_text">Unnamed Folder</string>
+
+    <!-- Accessibility -->
+    <skip />
+
+    <!-- The format string for Workspace descriptions [CHAR_LIMIT=none] -->
+    <string name="workspace_description_format">Home screen %1$d</string>
+
+    <!-- The format string for default page scroll text [CHAR_LIMIT=none] -->
+    <string name="default_scroll_format">Page %1$d of %2$d</string>
+    <!-- The format string for Workspace page scroll text [CHAR_LIMIT=none] -->
+    <string name="workspace_scroll_format">Home screen %1$d of %2$d</string>
+    <!-- The format string for AppsCustomize Apps page scroll text [CHAR_LIMIT=none] -->
+    <string name="apps_customize_apps_scroll_format">Apps page %1$d of %2$d</string>
+    <!-- The format string for AppsCustomize Apps page scroll text [CHAR_LIMIT=none] -->
+    <string name="apps_customize_widgets_scroll_format">Widgets page %1$d of %2$d</string>
+
+    <!-- Clings -->
+    <!-- The title text for the workspace cling [CHAR_LIMIT=60] -->
+    <string name="first_run_cling_title">Welcome!</string>
+    <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+    <string name="first_run_cling_description">Make yourself at home.</string>
+    <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+    <string name="first_run_cling_custom_content_hint"></string>
+    <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+    <string name="first_run_cling_search_bar_hint"></string>
+    <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+    <string name="first_run_cling_create_screens_hint">Create more screens for apps and folders</string>
+    <!-- The title text for the workspace cling [CHAR_LIMIT=60] -->
+    <string name="workspace_cling_title">Organize your space</string>
+    <!-- The description of how to use the workspace [CHAR_LIMIT=160] -->
+    <string name="workspace_cling_move_item">Touch &amp; hold background to manage wallpaper, widgets and settings.</string>
+    <!-- The title text for the All Apps cling [CHAR_LIMIT=60] -->
+    <string name="all_apps_cling_title">Choose some apps</string>
+    <!-- The description of how to pick up and add an item to the workspace [CHAR_LIMIT=160] -->
+    <string name="all_apps_cling_add_item">To add an app to your Home screen, touch &amp; hold it.</string>
+    <!-- The title text for the Folder cling [CHAR_LIMIT=60] -->
+    <string name="folder_cling_title">Here\'s a folder</string>
+    <!-- The description of how to create a folder [CHAR_LIMIT=160] -->
+    <string name="folder_cling_create_folder">To create one like this, touch &amp; hold an app, then move it over another.</string>
+    <!-- The text on the button to dismiss a cling [CHAR_LIMIT=30] -->
+    <string name="cling_dismiss">OK</string>
+    <!-- Error message on dummy custom cling layout [DO NOT TRANSLATE] -->
+    <string name="dummy_custom_cling_error_message">Error: custom workspace layout passed in but custom cling was not overwritten</string>
+    <add-resource type="string" name="default_folder_name" />
+
+    <!-- Folder accessibility -->
+    <!-- The format string for when a folder is opened, speaks the dimensions -->
+    <string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
+    <!-- Instruction that clicking outside will close folder -->
+    <string name="folder_tap_to_close">Touch to close folder</string>
+    <!-- Instruction that clicking outside will commit folder rename -->
+    <string name="folder_tap_to_rename">Touch to save rename</string>
+    <!-- Indication that folder closed -->
+    <string name="folder_closed">Folder closed</string>
+    <!-- Folder renamed format -->
+    <string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+    <!-- Folder name format -->
+    <string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+    <!-- Strings used in device overlays -->
+
+    <!-- Clings -->
+    <!-- Dummy string [CHAR_LIMIT=40] -->
+    <string name="custom_workspace_cling_title_1"></string>
+    <!-- Dummy string [CHAR_LIMIT=60] -->
+    <string name="custom_workspace_cling_description_1"></string>
+    <!-- Dummy string [CHAR_LIMIT=40] -->
+    <string name="custom_workspace_cling_title_2"></string>
+    <!-- Dummy string [CHAR_LIMIT=60] -->
+    <string name="custom_workspace_cling_description_2"></string>
+
+    <!-- Debug-only activity name. [DO NOT TRANSLATE] -->
+    <string name="debug_memory_activity">* HPROF</string>
+
+    <!-- Strings for the customization mode -->
+    <!-- Text for widget add button -->
+    <string name="widget_button_text">Widgets</string>
+    <!-- Text for wallpaper change button -->
+    <string name="wallpaper_button_text">Wallpapers</string>
+    <!-- Text for settings button -->
+    <string name="settings_button_text">Settings</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..a1d2c5c
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,169 @@
+<?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.
+*/
+-->
+
+<resources>
+    <style name="Theme.WallpaperCropper" parent="@android:style/Theme.Holo">
+        <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>
+
+    <style name="WallpaperCropperActionBar" parent="android:style/Widget.Holo.ActionBar">
+        <item name="android:displayOptions">showCustom</item>
+        <item name="android:background">#88000000</item>
+    </style>
+
+    <style name="Theme" parent="@android:style/Theme.Holo.Wallpaper.NoTitleBar">
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+    </style>
+
+    <style name="ClingButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">15dp</item>
+        <item name="android:paddingBottom">15dp</item>
+        <item name="android:paddingStart">50dp</item>
+        <item name="android:paddingEnd">50dp</item>
+        <item name="android:text">@string/cling_dismiss</item>
+        <item name="android:textColor">#ffffff</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:background">@drawable/cling_button_bg</item>
+    </style>
+    <style name="ClingTitleText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginBottom">5dp</item>
+        <item name="android:textSize">22sp</item>
+        <item name="android:textColor">#ffffff</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+    <style name="ClingAltTitleText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">24sp</item>
+        <item name="android:textColor">#49C0EC</item>
+    </style>
+    <style name="ClingText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">#80000000</item>
+        <item name="android:lineSpacingMultiplier">1.1</item>
+    </style>
+    <style name="ClingHintText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">#80ffffff</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="WorkspaceIcon">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center_horizontal</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:textColor">@color/workspace_icon_text_color</item>
+        <item name="android:shadowRadius">2.0</item>
+        <item name="android:shadowColor">#B0000000</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="WorkspaceIcon.Portrait">
+    </style>
+    <style name="WorkspaceIcon.Landscape">
+    </style>
+
+    <style name="WorkspaceIcon.AppsCustomize">
+        <item name="android:background">@null</item>
+        <item name="android:textColor">@color/apps_customize_icon_text_color</item>
+        <item name="android:drawablePadding">4dp</item>
+        <item name="android:shadowRadius">4.0</item>
+        <item name="android:shadowColor">#FF000000</item>
+    </style>
+
+    <style name="QSBBar">
+        <item name="android:orientation">horizontal</item>
+    </style>
+    <style name="SearchDropTargetBar">
+    </style>
+    <style name="SearchButton">
+    </style>
+    <style name="DropTargetButtonContainer">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+    <style name="DropTargetButton">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:drawablePadding">7.5dp</item>
+        <item name="android:paddingStart">25dp</item>
+        <item name="android:paddingEnd">25dp</item>
+        <item name="android:textColor">#FFFFFFFF</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:shadowColor">#FF000000</item>
+        <item name="android:shadowDx">0.0</item>
+        <item name="android:shadowDy">1.0</item>
+        <item name="android:shadowRadius">4.0</item>
+    </style>
+
+    <style name="TabIndicator">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingStart">20dp</item>
+        <item name="android:paddingEnd">20dp</item>
+        <item name="android:background">@drawable/tab_widget_indicator_selector</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+    <style name="TabIndicator.AppsCustomize">
+        <!-- Overridden in values-land -->
+    </style>
+
+    <style name="MarketButton">
+        <item name="android:paddingStart">5dp</item>
+        <item name="android:paddingEnd">5dp</item>
+        <item name="android:textColor">@color/workspace_all_apps_and_delete_zone_text_color</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:shadowColor">@color/workspace_all_apps_and_delete_zone_text_shadow_color</item>
+        <item name="android:shadowDx">0.0</item>
+        <item name="android:shadowDy">0.0</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+
+    <!-- Overridden in device overlays -->
+    <style name="CustomClingTitleText">
+    </style>
+    <style name="CustomClingText">
+    </style>
+</resources>
diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml-sw600dp/default_workspace.xml
new file mode 100644
index 0000000..090c7a7
--- /dev/null
+++ b/res/xml-sw600dp/default_workspace.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Far-left screen [0] -->
+
+    <!-- Left screen [1] -->
+    <appwidget
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+        launcher:screen="1"
+        launcher:x="0"
+        launcher:y="3"
+        launcher:spanX="4"
+        launcher:spanY="1" />
+
+    <!-- Middle screen [2] -->
+    <appwidget
+        launcher:packageName="com.android.deskclock"
+        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+        launcher:screen="2"
+        launcher:x="1"
+        launcher:y="0"
+        launcher:spanX="2"
+        launcher:spanY="2" />
+    <favorite
+        launcher:packageName="com.android.camera"
+        launcher:className="com.android.camera.Camera"
+        launcher:screen="2"
+        launcher:x="0"
+        launcher:y="3" />
+
+    <!-- Right screen [3] -->
+    <favorite
+        launcher:packageName="com.android.gallery3d"
+        launcher:className="com.android.gallery3d.app.Gallery"
+        launcher:screen="3"
+        launcher:x="1"
+        launcher:y="3" />
+    <favorite
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.Settings"
+        launcher:screen="3"
+        launcher:x="2"
+        launcher:y="3" />
+
+    <!-- Far-right screen [4] -->
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <favorite
+        launcher:packageName="com.android.dialer"
+        launcher:className="com.android.dialer.DialtactsActivity"
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.contacts"
+        launcher:className="com.android.contacts.activities.PeopleActivity"
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.mms"
+        launcher:className="com.android.mms.ui.ConversationList"
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.browser"
+        launcher:className="com.android.browser.BrowserActivity"
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" />
+</favorites>
diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml
new file mode 100644
index 0000000..1c1d70e
--- /dev/null
+++ b/res/xml-sw720dp/default_workspace.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Far-left screen [0] -->
+
+    <!-- Left screen [1] -->
+    <appwidget
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+        launcher:screen="1"
+        launcher:x="0"
+        launcher:y="3"
+        launcher:spanX="4"
+        launcher:spanY="1" />
+
+    <!-- Middle screen [2] -->
+    <appwidget
+        launcher:packageName="com.android.deskclock"
+        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+        launcher:screen="2"
+        launcher:x="1"
+        launcher:y="0"
+        launcher:spanX="2"
+        launcher:spanY="2" />
+    <favorite
+        launcher:packageName="com.android.camera"
+        launcher:className="com.android.camera.Camera"
+        launcher:screen="2"
+        launcher:x="0"
+        launcher:y="3" />
+
+    <!-- Right screen [3] -->
+    <favorite
+        launcher:packageName="com.android.gallery3d"
+        launcher:className="com.android.gallery3d.app.Gallery"
+        launcher:screen="3"
+        launcher:x="1"
+        launcher:y="3" />
+    <favorite
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.Settings"
+        launcher:screen="3"
+        launcher:x="2"
+        launcher:y="3" />
+
+    <!-- Far-right screen [4] -->
+</favorites>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
new file mode 100644
index 0000000..26fc504
--- /dev/null
+++ b/res/xml/default_workspace.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Far-left screen [0] -->
+
+    <!-- Left screen [1] -->
+    <appwidget
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+        launcher:screen="1"
+        launcher:x="0"
+        launcher:y="3"
+        launcher:spanX="4"
+        launcher:spanY="1" />
+
+    <!-- Middle screen [2] -->
+    <appwidget
+        launcher:packageName="com.android.deskclock"
+        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+        launcher:screen="2"
+        launcher:x="1"
+        launcher:y="0"
+        launcher:spanX="2"
+        launcher:spanY="2" />
+    <favorite
+        launcher:packageName="com.android.camera"
+        launcher:className="com.android.camera.Camera"
+        launcher:screen="2"
+        launcher:x="0"
+        launcher:y="3" />
+
+    <!-- Right screen [3] -->
+    <favorite
+        launcher:packageName="com.android.gallery3d"
+        launcher:className="com.android.gallery3d.app.Gallery"
+        launcher:screen="3"
+        launcher:x="1"
+        launcher:y="3" />
+    <favorite
+        launcher:packageName="com.android.settings"
+        launcher:className="com.android.settings.Settings"
+        launcher:screen="3"
+        launcher:x="2"
+        launcher:y="3" />
+
+    <!-- Far-right screen [4] -->
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <favorite
+        launcher:packageName="com.android.dialer"
+        launcher:className="com.android.dialer.DialtactsActivity"
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.contacts"
+        launcher:className="com.android.contacts.activities.PeopleActivity"
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.mms"
+        launcher:className="com.android.mms.ui.ConversationList"
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.browser"
+        launcher:className="com.android.browser.BrowserActivity"
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" />
+</favorites>
diff --git a/res/xml/update_workspace.xml b/res/xml/update_workspace.xml
new file mode 100644
index 0000000..38442b9
--- /dev/null
+++ b/res/xml/update_workspace.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Update the db with new hotseat items.  Note that we reference the browser's original
+         package name. -->
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <favorite
+        launcher:packageName="com.android.dialer"
+        launcher:className="com.android.dialer.DialtactsActivity"
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.contacts"
+        launcher:className="com.android.contacts.activities.PeopleActivity"
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.mms"
+        launcher:className="com.android.mms.ui.ConversationList"
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.android.browser"
+        launcher:className="com.android.browser.BrowserActivity"
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" />
+</favorites>
diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644
index 0000000..40bab1e
--- /dev/null
+++ b/src/android/util/Pools.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 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 android.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/common/BitmapUtils.java b/src/com/android/gallery3d/common/BitmapUtils.java
new file mode 100644
index 0000000..a671ed2
--- /dev/null
+++ b/src/com/android/gallery3d/common/BitmapUtils.java
@@ -0,0 +1,260 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.os.Build;
+import android.util.FloatMath;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class BitmapUtils {
+    private static final String TAG = "BitmapUtils";
+    private static final int DEFAULT_JPEG_QUALITY = 90;
+    public static final int UNCONSTRAINED = -1;
+
+    private BitmapUtils(){}
+
+    /*
+     * Compute the sample size as a function of minSideLength
+     * and maxNumOfPixels.
+     * minSideLength is used to specify that minimal width or height of a
+     * bitmap.
+     * maxNumOfPixels is used to specify the maximal size in pixels that is
+     * tolerable in terms of memory usage.
+     *
+     * The function returns a sample size based on the constraints.
+     * Both size and minSideLength can be passed in as UNCONSTRAINED,
+     * which indicates no care of the corresponding constraint.
+     * The functions prefers returning a sample size that
+     * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
+     *
+     * Also, the function rounds up the sample size to a power of 2 or multiple
+     * of 8 because BitmapFactory only honors sample size this way.
+     * For example, BitmapFactory downsamples an image by 2 even though the
+     * request is 3. So we round up the sample size to avoid OOM.
+     */
+    public static int computeSampleSize(int width, int height,
+            int minSideLength, int maxNumOfPixels) {
+        int initialSize = computeInitialSampleSize(
+                width, height, minSideLength, maxNumOfPixels);
+
+        return initialSize <= 8
+                ? Utils.nextPowerOf2(initialSize)
+                : (initialSize + 7) / 8 * 8;
+    }
+
+    private static int computeInitialSampleSize(int w, int h,
+            int minSideLength, int maxNumOfPixels) {
+        if (maxNumOfPixels == UNCONSTRAINED
+                && minSideLength == UNCONSTRAINED) return 1;
+
+        int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
+                (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
+
+        if (minSideLength == UNCONSTRAINED) {
+            return lowerBound;
+        } else {
+            int sampleSize = Math.min(w / minSideLength, h / minSideLength);
+            return Math.max(sampleSize, lowerBound);
+        }
+    }
+
+    // This computes a sample size which makes the longer side at least
+    // minSideLength long. If that's not possible, return 1.
+    public static int computeSampleSizeLarger(int w, int h,
+            int minSideLength) {
+        int initialSize = Math.max(w / minSideLength, h / minSideLength);
+        if (initialSize <= 1) return 1;
+
+        return initialSize <= 8
+                ? Utils.prevPowerOf2(initialSize)
+                : initialSize / 8 * 8;
+    }
+
+    // Find the min x that 1 / x >= scale
+    public static int computeSampleSizeLarger(float scale) {
+        int initialSize = (int) FloatMath.floor(1f / scale);
+        if (initialSize <= 1) return 1;
+
+        return initialSize <= 8
+                ? Utils.prevPowerOf2(initialSize)
+                : initialSize / 8 * 8;
+    }
+
+    // Find the max x that 1 / x <= scale.
+    public static int computeSampleSize(float scale) {
+        Utils.assertTrue(scale > 0);
+        int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
+        return initialSize <= 8
+                ? Utils.nextPowerOf2(initialSize)
+                : (initialSize + 7) / 8 * 8;
+    }
+
+    public static Bitmap resizeBitmapByScale(
+            Bitmap bitmap, float scale, boolean recycle) {
+        int width = Math.round(bitmap.getWidth() * scale);
+        int height = Math.round(bitmap.getHeight() * scale);
+        if (width == bitmap.getWidth()
+                && height == bitmap.getHeight()) return bitmap;
+        Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
+        Canvas canvas = new Canvas(target);
+        canvas.scale(scale, scale);
+        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+        canvas.drawBitmap(bitmap, 0, 0, paint);
+        if (recycle) bitmap.recycle();
+        return target;
+    }
+
+    private static Bitmap.Config getConfig(Bitmap bitmap) {
+        Bitmap.Config config = bitmap.getConfig();
+        if (config == null) {
+            config = Bitmap.Config.ARGB_8888;
+        }
+        return config;
+    }
+
+    public static Bitmap resizeDownBySideLength(
+            Bitmap bitmap, int maxLength, boolean recycle) {
+        int srcWidth = bitmap.getWidth();
+        int srcHeight = bitmap.getHeight();
+        float scale = Math.min(
+                (float) maxLength / srcWidth, (float) maxLength / srcHeight);
+        if (scale >= 1.0f) return bitmap;
+        return resizeBitmapByScale(bitmap, scale, recycle);
+    }
+
+    public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        if (w == size && h == size) return bitmap;
+
+        // scale the image so that the shorter side equals to the target;
+        // the longer side will be center-cropped.
+        float scale = (float) size / Math.min(w,  h);
+
+        Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
+        int width = Math.round(scale * bitmap.getWidth());
+        int height = Math.round(scale * bitmap.getHeight());
+        Canvas canvas = new Canvas(target);
+        canvas.translate((size - width) / 2f, (size - height) / 2f);
+        canvas.scale(scale, scale);
+        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+        canvas.drawBitmap(bitmap, 0, 0, paint);
+        if (recycle) bitmap.recycle();
+        return target;
+    }
+
+    public static void recycleSilently(Bitmap bitmap) {
+        if (bitmap == null) return;
+        try {
+            bitmap.recycle();
+        } catch (Throwable t) {
+            Log.w(TAG, "unable recycle bitmap", t);
+        }
+    }
+
+    public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
+        if (rotation == 0) return source;
+        int w = source.getWidth();
+        int h = source.getHeight();
+        Matrix m = new Matrix();
+        m.postRotate(rotation);
+        Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
+        if (recycle) source.recycle();
+        return bitmap;
+    }
+
+    public static Bitmap createVideoThumbnail(String filePath) {
+        // MediaMetadataRetriever is available on API Level 8
+        // but is hidden until API Level 10
+        Class<?> clazz = null;
+        Object instance = null;
+        try {
+            clazz = Class.forName("android.media.MediaMetadataRetriever");
+            instance = clazz.newInstance();
+
+            Method method = clazz.getMethod("setDataSource", String.class);
+            method.invoke(instance, filePath);
+
+            // The method name changes between API Level 9 and 10.
+            if (Build.VERSION.SDK_INT <= 9) {
+                return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
+            } else {
+                byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
+                if (data != null) {
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    if (bitmap != null) return bitmap;
+                }
+                return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
+            }
+        } catch (IllegalArgumentException ex) {
+            // Assume this is a corrupt video file
+        } catch (RuntimeException ex) {
+            // Assume this is a corrupt video file.
+        } catch (InstantiationException e) {
+            Log.e(TAG, "createVideoThumbnail", e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "createVideoThumbnail", e);
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "createVideoThumbnail", e);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "createVideoThumbnail", e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "createVideoThumbnail", e);
+        } finally {
+            try {
+                if (instance != null) {
+                    clazz.getMethod("release").invoke(instance);
+                }
+            } catch (Exception ignored) {
+            }
+        }
+        return null;
+    }
+
+    public static byte[] compressToBytes(Bitmap bitmap) {
+        return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
+    }
+
+    public static byte[] compressToBytes(Bitmap bitmap, int quality) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
+        bitmap.compress(CompressFormat.JPEG, quality, baos);
+        return baos.toByteArray();
+    }
+
+    public static boolean isSupportedByRegionDecoder(String mimeType) {
+        if (mimeType == null) return false;
+        mimeType = mimeType.toLowerCase();
+        return mimeType.startsWith("image/") &&
+                (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
+    }
+
+    public static boolean isRotationSupported(String mimeType) {
+        if (mimeType == null) return false;
+        mimeType = mimeType.toLowerCase();
+        return mimeType.equals("image/jpeg");
+    }
+}
diff --git a/src/com/android/gallery3d/common/Utils.java b/src/com/android/gallery3d/common/Utils.java
new file mode 100644
index 0000000..614a081
--- /dev/null
+++ b/src/com/android/gallery3d/common/Utils.java
@@ -0,0 +1,340 @@
+/*
+ * 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.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+public class Utils {
+    private static final String TAG = "Utils";
+    private static final String DEBUG_TAG = "GalleryDebug";
+
+    private static final long POLY64REV = 0x95AC9329AC4BC9B5L;
+    private static final long INITIALCRC = 0xFFFFFFFFFFFFFFFFL;
+
+    private static long[] sCrcTable = new long[256];
+
+    private static final boolean IS_DEBUG_BUILD =
+            Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug");
+
+    private static final String MASK_STRING = "********************************";
+
+    // Throws AssertionError if the input is false.
+    public static void assertTrue(boolean cond) {
+        if (!cond) {
+            throw new AssertionError();
+        }
+    }
+
+    // Throws AssertionError with the message. We had a method having the form
+    //   assertTrue(boolean cond, String message, Object ... args);
+    // However a call to that method will cause memory allocation even if the
+    // condition is false (due to autoboxing generated by "Object ... args"),
+    // so we don't use that anymore.
+    public static void fail(String message, Object ... args) {
+        throw new AssertionError(
+                args.length == 0 ? message : String.format(message, args));
+    }
+
+    // Throws NullPointerException if the input is null.
+    public static <T> T checkNotNull(T object) {
+        if (object == null) throw new NullPointerException();
+        return object;
+    }
+
+    // Returns true if two input Object are both null or equal
+    // to each other.
+    public static boolean equals(Object a, Object b) {
+        return (a == b) || (a == null ? false : a.equals(b));
+    }
+
+    // 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;
+    }
+
+    // Returns the input value x clamped to the range [min, max].
+    public static float clamp(float x, float min, float max) {
+        if (x > max) return max;
+        if (x < min) return min;
+        return x;
+    }
+
+    // Returns the input value x clamped to the range [min, max].
+    public static long clamp(long x, long min, long max) {
+        if (x > max) return max;
+        if (x < min) return min;
+        return x;
+    }
+
+    public static boolean isOpaque(int color) {
+        return color >>> 24 == 0xFF;
+    }
+
+    public static void swap(int[] array, int i, int j) {
+        int temp = array[i];
+        array[i] = array[j];
+        array[j] = temp;
+    }
+
+    /**
+     * A function thats returns a 64-bit crc for string
+     *
+     * @param in input string
+     * @return a 64-bit crc value
+     */
+    public static final long crc64Long(String in) {
+        if (in == null || in.length() == 0) {
+            return 0;
+        }
+        return crc64Long(getBytes(in));
+    }
+
+    static {
+        // http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
+        long part;
+        for (int i = 0; i < 256; i++) {
+            part = i;
+            for (int j = 0; j < 8; j++) {
+                long x = ((int) part & 1) != 0 ? POLY64REV : 0;
+                part = (part >> 1) ^ x;
+            }
+            sCrcTable[i] = part;
+        }
+    }
+
+    public static final long crc64Long(byte[] buffer) {
+        long crc = INITIALCRC;
+        for (int k = 0, n = buffer.length; k < n; ++k) {
+            crc = sCrcTable[(((int) crc) ^ buffer[k]) & 0xff] ^ (crc >> 8);
+        }
+        return crc;
+    }
+
+    public static byte[] getBytes(String in) {
+        byte[] result = new byte[in.length() * 2];
+        int output = 0;
+        for (char ch : in.toCharArray()) {
+            result[output++] = (byte) (ch & 0xFF);
+            result[output++] = (byte) (ch >> 8);
+        }
+        return result;
+    }
+
+    public static void closeSilently(Closeable c) {
+        if (c == null) return;
+        try {
+            c.close();
+        } catch (IOException t) {
+            Log.w(TAG, "close fail ", t);
+        }
+    }
+
+    public static int compare(long a, long b) {
+        return a < b ? -1 : a == b ? 0 : 1;
+    }
+
+    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(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 float interpolateAngle(
+            float source, float target, float progress) {
+        // interpolate the angle from source to target
+        // We make the difference in the range of [-179, 180], this is the
+        // shortest path to change source to target.
+        float diff = target - source;
+        if (diff < 0) diff += 360f;
+        if (diff > 180) diff -= 360f;
+
+        float result = source + diff * progress;
+        return result < 0 ? result + 360f : result;
+    }
+
+    public static float interpolateScale(
+            float source, float target, float progress) {
+        return source + progress * (target - source);
+    }
+
+    public static String ensureNotNull(String value) {
+        return value == null ? "" : value;
+    }
+
+    public static float parseFloatSafely(String content, float defaultValue) {
+        if (content == null) return defaultValue;
+        try {
+            return Float.parseFloat(content);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    public static int parseIntSafely(String content, int defaultValue) {
+        if (content == null) return defaultValue;
+        try {
+            return Integer.parseInt(content);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    public static boolean isNullOrEmpty(String exifMake) {
+        return TextUtils.isEmpty(exifMake);
+    }
+
+    public static void waitWithoutInterrupt(Object object) {
+        try {
+            object.wait();
+        } catch (InterruptedException e) {
+            Log.w(TAG, "unexpected interrupt: " + object);
+        }
+    }
+
+    public static boolean handleInterrruptedException(Throwable e) {
+        // A helper to deal with the interrupt exception
+        // If an interrupt detected, we will setup the bit again.
+        if (e instanceof InterruptedIOException
+                || e instanceof InterruptedException) {
+            Thread.currentThread().interrupt();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return String with special XML characters escaped.
+     */
+    public static String escapeXml(String s) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0, len = s.length(); i < len; ++i) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '<':  sb.append("&lt;"); break;
+                case '>':  sb.append("&gt;"); break;
+                case '\"': sb.append("&quot;"); break;
+                case '\'': sb.append("&#039;"); break;
+                case '&':  sb.append("&amp;"); break;
+                default: sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String getUserAgent(Context context) {
+        PackageInfo packageInfo;
+        try {
+            packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            throw new IllegalStateException("getPackageInfo failed");
+        }
+        return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
+                packageInfo.packageName,
+                packageInfo.versionName,
+                Build.BRAND,
+                Build.DEVICE,
+                Build.MODEL,
+                Build.ID,
+                Build.VERSION.SDK_INT,
+                Build.VERSION.RELEASE,
+                Build.VERSION.INCREMENTAL);
+    }
+
+    public static String[] copyOf(String[] source, int newSize) {
+        String[] result = new String[newSize];
+        newSize = Math.min(source.length, newSize);
+        System.arraycopy(source, 0, result, 0, newSize);
+        return result;
+    }
+
+    // Mask information for debugging only. It returns <code>info.toString()</code> directly
+    // for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
+    // in release build to protect the information (e.g. for privacy issue).
+    public static String maskDebugInfo(Object info) {
+        if (info == null) return null;
+        String s = info.toString();
+        int length = Math.min(s.length(), MASK_STRING.length());
+        return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
+    }
+
+    // This method should be ONLY used for debugging.
+    public static void debug(String message, Object ... args) {
+        Log.v(DEBUG_TAG, String.format(message, args));
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/src/com/android/gallery3d/exif/ByteBufferInputStream.java
new file mode 100644
index 0000000..7fb9f22
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ByteBufferInputStream.java
@@ -0,0 +1,48 @@
+/*
+ * 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.exif;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+class ByteBufferInputStream extends InputStream {
+
+    private ByteBuffer mBuf;
+
+    public ByteBufferInputStream(ByteBuffer buf) {
+        mBuf = buf;
+    }
+
+    @Override
+    public int read() {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+        return mBuf.get() & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] bytes, int off, int len) {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+
+        len = Math.min(len, mBuf.remaining());
+        mBuf.get(bytes, off, len);
+        return len;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/CountedDataInputStream.java b/src/com/android/gallery3d/exif/CountedDataInputStream.java
new file mode 100644
index 0000000..dfd4a1a
--- /dev/null
+++ b/src/com/android/gallery3d/exif/CountedDataInputStream.java
@@ -0,0 +1,136 @@
+/*
+ * 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.exif;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class CountedDataInputStream extends FilterInputStream {
+
+    private int mCount = 0;
+
+    // allocate a byte buffer for a long value;
+    private final byte mByteArray[] = new byte[8];
+    private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+    protected CountedDataInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int getReadByteCount() {
+        return mCount;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        int r = in.read(b);
+        mCount += (r >= 0) ? r : 0;
+        return r;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        int r = in.read(b, off, len);
+        mCount += (r >= 0) ? r : 0;
+        return r;
+    }
+
+    @Override
+    public int read() throws IOException {
+        int r = in.read();
+        mCount += (r >= 0) ? 1 : 0;
+        return r;
+    }
+
+    @Override
+    public long skip(long length) throws IOException {
+        long skip = in.skip(length);
+        mCount += skip;
+        return skip;
+    }
+
+    public void skipOrThrow(long length) throws IOException {
+        if (skip(length) != length) throw new EOFException();
+    }
+
+    public void skipTo(long target) throws IOException {
+        long cur = mCount;
+        long diff = target - cur;
+        assert(diff >= 0);
+        skipOrThrow(diff);
+    }
+
+    public void readOrThrow(byte[] b, int off, int len) throws IOException {
+        int r = read(b, off, len);
+        if (r != len) throw new EOFException();
+    }
+
+    public void readOrThrow(byte[] b) throws IOException {
+        readOrThrow(b, 0, b.length);
+    }
+
+    public void setByteOrder(ByteOrder order) {
+        mByteBuffer.order(order);
+    }
+
+    public ByteOrder getByteOrder() {
+        return mByteBuffer.order();
+    }
+
+    public short readShort() throws IOException {
+        readOrThrow(mByteArray, 0 ,2);
+        mByteBuffer.rewind();
+        return mByteBuffer.getShort();
+    }
+
+    public int readUnsignedShort() throws IOException {
+        return readShort() & 0xffff;
+    }
+
+    public int readInt() throws IOException {
+        readOrThrow(mByteArray, 0 , 4);
+        mByteBuffer.rewind();
+        return mByteBuffer.getInt();
+    }
+
+    public long readUnsignedInt() throws IOException {
+        return readInt() & 0xffffffffL;
+    }
+
+    public long readLong() throws IOException {
+        readOrThrow(mByteArray, 0 , 8);
+        mByteBuffer.rewind();
+        return mByteBuffer.getLong();
+    }
+
+    public String readString(int n) throws IOException {
+        byte buf[] = new byte[n];
+        readOrThrow(buf);
+        return new String(buf, "UTF8");
+    }
+
+    public String readString(int n, Charset charset) throws IOException {
+        byte buf[] = new byte[n];
+        readOrThrow(buf);
+        return new String(buf, charset);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
new file mode 100644
index 0000000..8422382
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifData.java
@@ -0,0 +1,348 @@
+/*
+ * 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.exif;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class stores the EXIF header in IFDs according to the JPEG
+ * specification. It is the result produced by {@link ExifReader}.
+ *
+ * @see ExifReader
+ * @see IfdData
+ */
+class ExifData {
+    private static final String TAG = "ExifData";
+    private static final byte[] USER_COMMENT_ASCII = {
+            0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_JIS = {
+            0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_UNICODE = {
+            0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+    };
+
+    private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
+    private byte[] mThumbnail;
+    private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
+    private final ByteOrder mByteOrder;
+
+    ExifData(ByteOrder order) {
+        mByteOrder = order;
+    }
+
+    /**
+     * Gets the compressed thumbnail. Returns null if there is no compressed
+     * thumbnail.
+     *
+     * @see #hasCompressedThumbnail()
+     */
+    protected byte[] getCompressedThumbnail() {
+        return mThumbnail;
+    }
+
+    /**
+     * Sets the compressed thumbnail.
+     */
+    protected void setCompressedThumbnail(byte[] thumbnail) {
+        mThumbnail = thumbnail;
+    }
+
+    /**
+     * Returns true it this header contains a compressed thumbnail.
+     */
+    protected boolean hasCompressedThumbnail() {
+        return mThumbnail != null;
+    }
+
+    /**
+     * Adds an uncompressed strip.
+     */
+    protected void setStripBytes(int index, byte[] strip) {
+        if (index < mStripBytes.size()) {
+            mStripBytes.set(index, strip);
+        } else {
+            for (int i = mStripBytes.size(); i < index; i++) {
+                mStripBytes.add(null);
+            }
+            mStripBytes.add(strip);
+        }
+    }
+
+    /**
+     * Gets the strip count.
+     */
+    protected int getStripCount() {
+        return mStripBytes.size();
+    }
+
+    /**
+     * Gets the strip at the specified index.
+     *
+     * @exceptions #IndexOutOfBoundException
+     */
+    protected byte[] getStrip(int index) {
+        return mStripBytes.get(index);
+    }
+
+    /**
+     * Returns true if this header contains uncompressed strip.
+     */
+    protected boolean hasUncompressedStrip() {
+        return mStripBytes.size() != 0;
+    }
+
+    /**
+     * Gets the byte order.
+     */
+    protected ByteOrder getByteOrder() {
+        return mByteOrder;
+    }
+
+    /**
+     * Returns the {@link IfdData} object corresponding to a given IFD if it
+     * exists or null.
+     */
+    protected IfdData getIfdData(int ifdId) {
+        if (ExifTag.isValidIfd(ifdId)) {
+            return mIfdDatas[ifdId];
+        }
+        return null;
+    }
+
+    /**
+     * Adds IFD data. If IFD data of the same type already exists, it will be
+     * replaced by the new data.
+     */
+    protected void addIfdData(IfdData data) {
+        mIfdDatas[data.getId()] = data;
+    }
+
+    /**
+     * Returns the {@link IfdData} object corresponding to a given IFD or
+     * generates one if none exist.
+     */
+    protected IfdData getOrCreateIfdData(int ifdId) {
+        IfdData ifdData = mIfdDatas[ifdId];
+        if (ifdData == null) {
+            ifdData = new IfdData(ifdId);
+            mIfdDatas[ifdId] = ifdData;
+        }
+        return ifdData;
+    }
+
+    /**
+     * Returns the tag with a given TID in the given IFD if the tag exists.
+     * Otherwise returns null.
+     */
+    protected ExifTag getTag(short tag, int ifd) {
+        IfdData ifdData = mIfdDatas[ifd];
+        return (ifdData == null) ? null : ifdData.getTag(tag);
+    }
+
+    /**
+     * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
+     */
+    protected ExifTag addTag(ExifTag tag) {
+        if (tag != null) {
+            int ifd = tag.getIfd();
+            return addTag(tag, ifd);
+        }
+        return null;
+    }
+
+    /**
+     * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
+     */
+    protected ExifTag addTag(ExifTag tag, int ifdId) {
+        if (tag != null && ExifTag.isValidIfd(ifdId)) {
+            IfdData ifdData = getOrCreateIfdData(ifdId);
+            return ifdData.setTag(tag);
+        }
+        return null;
+    }
+
+    protected void clearThumbnailAndStrips() {
+        mThumbnail = null;
+        mStripBytes.clear();
+    }
+
+    /**
+     * Removes the thumbnail and its related tags. IFD1 will be removed.
+     */
+    protected void removeThumbnailData() {
+        clearThumbnailAndStrips();
+        mIfdDatas[IfdId.TYPE_IFD_1] = null;
+    }
+
+    /**
+     * Removes the tag with a given TID and IFD.
+     */
+    protected void removeTag(short tagId, int ifdId) {
+        IfdData ifdData = mIfdDatas[ifdId];
+        if (ifdData == null) {
+            return;
+        }
+        ifdData.removeTag(tagId);
+    }
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    protected String getUserComment() {
+        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
+        if (ifdData == null) {
+            return null;
+        }
+        ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+        if (tag == null) {
+            return null;
+        }
+        if (tag.getComponentCount() < 8) {
+            return null;
+        }
+
+        byte[] buf = new byte[tag.getComponentCount()];
+        tag.getBytes(buf);
+
+        byte[] code = new byte[8];
+        System.arraycopy(buf, 0, code, 0, 8);
+
+        try {
+            if (Arrays.equals(code, USER_COMMENT_ASCII)) {
+                return new String(buf, 8, buf.length - 8, "US-ASCII");
+            } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
+                return new String(buf, 8, buf.length - 8, "EUC-JP");
+            } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
+                return new String(buf, 8, buf.length - 8, "UTF-16");
+            } else {
+                return null;
+            }
+        } catch (UnsupportedEncodingException e) {
+            Log.w(TAG, "Failed to decode the user comment");
+            return null;
+        }
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTags() {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag[] tags = d.getAllTags();
+                if (tags != null) {
+                    for (ExifTag t : tags) {
+                        ret.add(t);
+                    }
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForIfd(int ifd) {
+        IfdData d = mIfdDatas[ifd];
+        if (d == null) {
+            return null;
+        }
+        ExifTag[] tags = d.getAllTags();
+        if (tags == null) {
+            return null;
+        }
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+        for (ExifTag t : tags) {
+            ret.add(t);
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s with a given TID or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForTagId(short tag) {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag t = d.getTag(tag);
+                if (t != null) {
+                    ret.add(t);
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof ExifData) {
+            ExifData data = (ExifData) obj;
+            if (data.mByteOrder != mByteOrder ||
+                    data.mStripBytes.size() != mStripBytes.size() ||
+                    !Arrays.equals(data.mThumbnail, mThumbnail)) {
+                return false;
+            }
+            for (int i = 0; i < mStripBytes.size(); i++) {
+                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+                    return false;
+                }
+            }
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                IfdData ifd1 = data.getIfdData(i);
+                IfdData ifd2 = getIfdData(i);
+                if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/android/gallery3d/exif/ExifInterface.java b/src/com/android/gallery3d/exif/ExifInterface.java
new file mode 100644
index 0000000..a1cf0fc
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifInterface.java
@@ -0,0 +1,2407 @@
+/*
+ * 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.gallery3d.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+    public static final int TAG_NULL = -1;
+    public static final int IFD_NULL = -1;
+    public static final int DEFINITION_NULL = 0;
+
+    /**
+     * Tag constants for Jeita EXIF 2.2
+     */
+
+    // IFD 0
+    public static final int TAG_IMAGE_WIDTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+    public static final int TAG_IMAGE_LENGTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+    public static final int TAG_BITS_PER_SAMPLE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+    public static final int TAG_COMPRESSION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+    public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+    public static final int TAG_IMAGE_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+    public static final int TAG_MAKE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+    public static final int TAG_MODEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+    public static final int TAG_STRIP_OFFSETS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+    public static final int TAG_ORIENTATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+    public static final int TAG_SAMPLES_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+    public static final int TAG_ROWS_PER_STRIP =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+    public static final int TAG_STRIP_BYTE_COUNTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+    public static final int TAG_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+    public static final int TAG_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+    public static final int TAG_PLANAR_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+    public static final int TAG_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+    public static final int TAG_TRANSFER_FUNCTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+    public static final int TAG_SOFTWARE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+    public static final int TAG_DATE_TIME =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+    public static final int TAG_ARTIST =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+    public static final int TAG_WHITE_POINT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+    public static final int TAG_PRIMARY_CHROMATICITIES =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+    public static final int TAG_Y_CB_CR_COEFFICIENTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+    public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+    public static final int TAG_Y_CB_CR_POSITIONING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+    public static final int TAG_REFERENCE_BLACK_WHITE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+    public static final int TAG_COPYRIGHT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+    public static final int TAG_EXIF_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+    public static final int TAG_GPS_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+    // IFD 1
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+    // IFD Exif Tags
+    public static final int TAG_EXPOSURE_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+    public static final int TAG_F_NUMBER =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+    public static final int TAG_EXPOSURE_PROGRAM =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+    public static final int TAG_SPECTRAL_SENSITIVITY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+    public static final int TAG_ISO_SPEED_RATINGS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+    public static final int TAG_OECF =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+    public static final int TAG_EXIF_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+    public static final int TAG_DATE_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+    public static final int TAG_DATE_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+    public static final int TAG_COMPONENTS_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+    public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+    public static final int TAG_SHUTTER_SPEED_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+    public static final int TAG_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+    public static final int TAG_BRIGHTNESS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+    public static final int TAG_EXPOSURE_BIAS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+    public static final int TAG_MAX_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+    public static final int TAG_SUBJECT_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+    public static final int TAG_METERING_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+    public static final int TAG_LIGHT_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+    public static final int TAG_FLASH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+    public static final int TAG_FOCAL_LENGTH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+    public static final int TAG_SUBJECT_AREA =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+    public static final int TAG_MAKER_NOTE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+    public static final int TAG_USER_COMMENT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+    public static final int TAG_SUB_SEC_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+    public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+    public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+    public static final int TAG_FLASHPIX_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+    public static final int TAG_COLOR_SPACE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+    public static final int TAG_PIXEL_X_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+    public static final int TAG_PIXEL_Y_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+    public static final int TAG_RELATED_SOUND_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+    public static final int TAG_INTEROPERABILITY_IFD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+    public static final int TAG_FLASH_ENERGY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+    public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+    public static final int TAG_SUBJECT_LOCATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+    public static final int TAG_EXPOSURE_INDEX =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+    public static final int TAG_SENSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+    public static final int TAG_FILE_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+    public static final int TAG_SCENE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+    public static final int TAG_CFA_PATTERN =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+    public static final int TAG_CUSTOM_RENDERED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+    public static final int TAG_EXPOSURE_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+    public static final int TAG_WHITE_BALANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+    public static final int TAG_DIGITAL_ZOOM_RATIO =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+    public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+    public static final int TAG_SCENE_CAPTURE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+    public static final int TAG_GAIN_CONTROL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+    public static final int TAG_CONTRAST =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+    public static final int TAG_SATURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+    public static final int TAG_SHARPNESS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+    public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+    public static final int TAG_SUBJECT_DISTANCE_RANGE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+    public static final int TAG_IMAGE_UNIQUE_ID =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+    // IFD GPS tags
+    public static final int TAG_GPS_VERSION_ID =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+    public static final int TAG_GPS_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+    public static final int TAG_GPS_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+    public static final int TAG_GPS_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+    public static final int TAG_GPS_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+    public static final int TAG_GPS_ALTITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+    public static final int TAG_GPS_ALTITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+    public static final int TAG_GPS_TIME_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+    public static final int TAG_GPS_SATTELLITES =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+    public static final int TAG_GPS_STATUS =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+    public static final int TAG_GPS_MEASURE_MODE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+    public static final int TAG_GPS_DOP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+    public static final int TAG_GPS_SPEED_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+    public static final int TAG_GPS_SPEED =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+    public static final int TAG_GPS_TRACK_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+    public static final int TAG_GPS_TRACK =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+    public static final int TAG_GPS_IMG_DIRECTION_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+    public static final int TAG_GPS_IMG_DIRECTION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+    public static final int TAG_GPS_MAP_DATUM =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+    public static final int TAG_GPS_DEST_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+    public static final int TAG_GPS_DEST_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+    public static final int TAG_GPS_DEST_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+    public static final int TAG_GPS_DEST_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+    public static final int TAG_GPS_DEST_BEARING_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+    public static final int TAG_GPS_DEST_BEARING =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+    public static final int TAG_GPS_DEST_DISTANCE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+    public static final int TAG_GPS_DEST_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+    public static final int TAG_GPS_PROCESSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+    public static final int TAG_GPS_AREA_INFORMATION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+    public static final int TAG_GPS_DATE_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+    public static final int TAG_GPS_DIFFERENTIAL =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+    // IFD Interoperability tags
+    public static final int TAG_INTEROPERABILITY_INDEX =
+        defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+    /**
+     * Tags that contain offset markers. These are included in the banned
+     * defines.
+     */
+    private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+    static {
+        sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+        sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+    }
+
+    /**
+     * Tags with definitions that cannot be overridden (banned defines).
+     */
+    protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+    static {
+        sBannedDefines.add(getTrueTagKey(TAG_NULL));
+        sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+    }
+
+    /**
+     * Returns the constant representing a tag with a given TID and default IFD.
+     */
+    public static int defineTag(int ifdId, short tagId) {
+        return (tagId & 0x0000ffff) | (ifdId << 16);
+    }
+
+    /**
+     * Returns the TID for a tag constant.
+     */
+    public static short getTrueTagKey(int tag) {
+        // Truncate
+        return (short) tag;
+    }
+
+    /**
+     * Returns the default IFD for a tag constant.
+     */
+    public static int getTrueIfd(int tag) {
+        return tag >>> 16;
+    }
+
+    /**
+     * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+     * follows:
+     * <ul>
+     * <li>TOP_LEFT is the normal orientation.</li>
+     * <li>TOP_RIGHT is a left-right mirror.</li>
+     * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+     * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+     * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+     * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+     * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+     * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+     * </ul>
+     */
+    public static interface Orientation {
+        public static final short TOP_LEFT = 1;
+        public static final short TOP_RIGHT = 2;
+        public static final short BOTTOM_LEFT = 3;
+        public static final short BOTTOM_RIGHT = 4;
+        public static final short LEFT_TOP = 5;
+        public static final short RIGHT_TOP = 6;
+        public static final short LEFT_BOTTOM = 7;
+        public static final short RIGHT_BOTTOM = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+     */
+    public static interface YCbCrPositioning {
+        public static final short CENTERED = 1;
+        public static final short CO_SITED = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPRESSION}
+     */
+    public static interface Compression {
+        public static final short UNCOMPRESSION = 1;
+        public static final short JPEG = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_RESOLUTION_UNIT}
+     */
+    public static interface ResolutionUnit {
+        public static final short INCHES = 2;
+        public static final short CENTIMETERS = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+     */
+    public static interface PhotometricInterpretation {
+        public static final short RGB = 2;
+        public static final short YCBCR = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_PLANAR_CONFIGURATION}
+     */
+    public static interface PlanarConfiguration {
+        public static final short CHUNKY = 1;
+        public static final short PLANAR = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_PROGRAM}
+     */
+    public static interface ExposureProgram {
+        public static final short NOT_DEFINED = 0;
+        public static final short MANUAL = 1;
+        public static final short NORMAL_PROGRAM = 2;
+        public static final short APERTURE_PRIORITY = 3;
+        public static final short SHUTTER_PRIORITY = 4;
+        public static final short CREATIVE_PROGRAM = 5;
+        public static final short ACTION_PROGRAM = 6;
+        public static final short PROTRAIT_MODE = 7;
+        public static final short LANDSCAPE_MODE = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_METERING_MODE}
+     */
+    public static interface MeteringMode {
+        public static final short UNKNOWN = 0;
+        public static final short AVERAGE = 1;
+        public static final short CENTER_WEIGHTED_AVERAGE = 2;
+        public static final short SPOT = 3;
+        public static final short MULTISPOT = 4;
+        public static final short PATTERN = 5;
+        public static final short PARTAIL = 6;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+     * standard, we can treat this constant as bitwise flag.
+     * <p>
+     * e.g.
+     * <p>
+     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+     * MODE_AUTO_MODE
+     */
+    public static interface Flash {
+        // LSB
+        public static final short DID_NOT_FIRED = 0;
+        public static final short FIRED = 1;
+        // 1st~2nd bits
+        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+        // 3rd~4th bits
+        public static final short MODE_UNKNOWN = 0 << 3;
+        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+        public static final short MODE_AUTO_MODE = 3 << 3;
+        // 5th bit
+        public static final short FUNCTION_PRESENT = 0 << 5;
+        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+        // 6th bit
+        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+    }
+
+    /**
+     * Constants for {@link TAG_COLOR_SPACE}
+     */
+    public static interface ColorSpace {
+        public static final short SRGB = 1;
+        public static final short UNCALIBRATED = (short) 0xFFFF;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_MODE}
+     */
+    public static interface ExposureMode {
+        public static final short AUTO_EXPOSURE = 0;
+        public static final short MANUAL_EXPOSURE = 1;
+        public static final short AUTO_BRACKET = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_WHITE_BALANCE}
+     */
+    public static interface WhiteBalance {
+        public static final short AUTO = 0;
+        public static final short MANUAL = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+     */
+    public static interface SceneCapture {
+        public static final short STANDARD = 0;
+        public static final short LANDSCAPE = 1;
+        public static final short PROTRAIT = 2;
+        public static final short NIGHT_SCENE = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+     */
+    public static interface ComponentsConfiguration {
+        public static final short NOT_EXIST = 0;
+        public static final short Y = 1;
+        public static final short CB = 2;
+        public static final short CR = 3;
+        public static final short R = 4;
+        public static final short G = 5;
+        public static final short B = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_LIGHT_SOURCE}
+     */
+    public static interface LightSource {
+        public static final short UNKNOWN = 0;
+        public static final short DAYLIGHT = 1;
+        public static final short FLUORESCENT = 2;
+        public static final short TUNGSTEN = 3;
+        public static final short FLASH = 4;
+        public static final short FINE_WEATHER = 9;
+        public static final short CLOUDY_WEATHER = 10;
+        public static final short SHADE = 11;
+        public static final short DAYLIGHT_FLUORESCENT = 12;
+        public static final short DAY_WHITE_FLUORESCENT = 13;
+        public static final short COOL_WHITE_FLUORESCENT = 14;
+        public static final short WHITE_FLUORESCENT = 15;
+        public static final short STANDARD_LIGHT_A = 17;
+        public static final short STANDARD_LIGHT_B = 18;
+        public static final short STANDARD_LIGHT_C = 19;
+        public static final short D55 = 20;
+        public static final short D65 = 21;
+        public static final short D75 = 22;
+        public static final short D50 = 23;
+        public static final short ISO_STUDIO_TUNGSTEN = 24;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_SENSING_METHOD}
+     */
+    public static interface SensingMethod {
+        public static final short NOT_DEFINED = 1;
+        public static final short ONE_CHIP_COLOR = 2;
+        public static final short TWO_CHIP_COLOR = 3;
+        public static final short THREE_CHIP_COLOR = 4;
+        public static final short COLOR_SEQUENTIAL_AREA = 5;
+        public static final short TRILINEAR = 7;
+        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_FILE_SOURCE}
+     */
+    public static interface FileSource {
+        public static final short DSC = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_TYPE}
+     */
+    public static interface SceneType {
+        public static final short DIRECT_PHOTOGRAPHED = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GAIN_CONTROL}
+     */
+    public static interface GainControl {
+        public static final short NONE = 0;
+        public static final short LOW_UP = 1;
+        public static final short HIGH_UP = 2;
+        public static final short LOW_DOWN = 3;
+        public static final short HIGH_DOWN = 4;
+    }
+
+    /**
+     * Constants for {@link TAG_CONTRAST}
+     */
+    public static interface Contrast {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SATURATION}
+     */
+    public static interface Saturation {
+        public static final short NORMAL = 0;
+        public static final short LOW = 1;
+        public static final short HIGH = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SHARPNESS}
+     */
+    public static interface Sharpness {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SUBJECT_DISTANCE}
+     */
+    public static interface SubjectDistance {
+        public static final short UNKNOWN = 0;
+        public static final short MACRO = 1;
+        public static final short CLOSE_VIEW = 2;
+        public static final short DISTANT_VIEW = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LATITUDE_REF},
+     * {@link TAG_GPS_DEST_LATITUDE_REF}
+     */
+    public static interface GpsLatitudeRef {
+        public static final String NORTH = "N";
+        public static final String SOUTH = "S";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LONGITUDE_REF},
+     * {@link TAG_GPS_DEST_LONGITUDE_REF}
+     */
+    public static interface GpsLongitudeRef {
+        public static final String EAST = "E";
+        public static final String WEST = "W";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_ALTITUDE_REF}
+     */
+    public static interface GpsAltitudeRef {
+        public static final short SEA_LEVEL = 0;
+        public static final short SEA_LEVEL_NEGATIVE = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_STATUS}
+     */
+    public static interface GpsStatus {
+        public static final String IN_PROGRESS = "A";
+        public static final String INTEROPERABILITY = "V";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_MEASURE_MODE}
+     */
+    public static interface GpsMeasureMode {
+        public static final String MODE_2_DIMENSIONAL = "2";
+        public static final String MODE_3_DIMENSIONAL = "3";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_SPEED_REF},
+     * {@link TAG_GPS_DEST_DISTANCE_REF}
+     */
+    public static interface GpsSpeedRef {
+        public static final String KILOMETERS = "K";
+        public static final String MILES = "M";
+        public static final String KNOTS = "N";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_TRACK_REF},
+     * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+     */
+    public static interface GpsTrackRef {
+        public static final String TRUE_DIRECTION = "T";
+        public static final String MAGNETIC_DIRECTION = "M";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_DIFFERENTIAL}
+     */
+    public static interface GpsDifferential {
+        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+    }
+
+    private static final String NULL_ARGUMENT_STRING = "Argument is null";
+    private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+    public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+    public ExifInterface() {
+        mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    /**
+     * Reads the exif tags from a byte array, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(byte[] jpeg) throws IOException {
+        readExif(new ByteArrayInputStream(jpeg));
+    }
+
+    /**
+     * Reads the exif tags from an InputStream, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param inStream an InputStream containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(InputStream inStream) throws IOException {
+        if (inStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifData d = null;
+        try {
+            d = new ExifReader(this).read(inStream);
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+        mData = d;
+    }
+
+    /**
+     * Reads the exif tags from a file, clearing this ExifInterface object's
+     * existing exif tags.
+     *
+     * @param inFileName a string representing the filepath to jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void readExif(String inFileName) throws FileNotFoundException, IOException {
+        if (inFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName));
+            readExif(is);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Sets the exif tags, clearing this ExifInterface object's existing exif
+     * tags.
+     *
+     * @param tags a collection of exif tags to set.
+     */
+    public void setExif(Collection<ExifTag> tags) {
+        clearExif();
+        setTags(tags);
+    }
+
+    /**
+     * Clears this ExifInterface object's existing exif tags.
+     */
+    public void clearExif() {
+        mData = new ExifData(DEFAULT_BYTE_ORDER);
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+        if (jpeg == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        s.write(jpeg, 0, jpeg.length);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutStream the OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+        if (bmap == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+        if (jpegStream == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        doExifStreamIO(jpegStream, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (jpeg == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            s.write(jpeg, 0, jpeg.length);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (bmap == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegStream == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            doExifStreamIO(jpegStream, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg file, removing
+     * prior exif tags.
+     *
+     * @param jpegFileName a String containing the filepath for a jpeg file.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(String jpegFileName, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegFileName == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = new FileInputStream(jpegFileName);
+            writeExif(is, exifOutFileName);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param outStream an OutputStream to wrap.
+     * @return an OutputStream that wraps the outStream parameter, and adds exif
+     *         metadata. A jpeg image should be written to this stream.
+     */
+    public OutputStream getExifWriterStream(OutputStream outStream) {
+        if (outStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifOutputStream eos = new ExifOutputStream(outStream, this);
+        eos.setExifData(mData);
+        return eos;
+    }
+
+    /**
+     * Returns an OutputStream object that writes to a file. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param exifOutFileName an String containing a filepath for a jpeg file.
+     * @return an OutputStream that writes to the exifOutFileName file, and adds
+     *         exif metadata. A jpeg image should be written to this stream.
+     * @throws FileNotFoundException
+     */
+    public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+        if (exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream out = null;
+        try {
+            out = (OutputStream) new FileOutputStream(exifOutFileName);
+        } catch (FileNotFoundException e) {
+            closeSilently(out);
+            throw e;
+        }
+        return getExifWriterStream(out);
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a file for the
+     * given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param filename a String containing a filepath for a jpeg file with exif
+     *            tags to rewrite.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException, IOException {
+        RandomAccessFile file = null;
+        InputStream is = null;
+        boolean ret;
+        try {
+            File temp = new File(filename);
+            is = new BufferedInputStream(new FileInputStream(temp));
+
+            // Parse beginning of APP1 in exif to find size of exif header.
+            ExifParser parser = null;
+            try {
+                parser = ExifParser.parse(is, this);
+            } catch (ExifInvalidFormatException e) {
+                throw new IOException("Invalid exif format : ", e);
+            }
+            long exifSize = parser.getOffsetToExifEndFromSOF();
+
+            // Free up resources
+            is.close();
+            is = null;
+
+            // Open file for memory mapping.
+            file = new RandomAccessFile(temp, "rw");
+            long fileLength = file.length();
+            if (fileLength < exifSize) {
+                throw new IOException("Filesize changed during operation");
+            }
+
+            // Map only exif header into memory.
+            ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+            // Attempt to overwrite tag values without changing lengths (avoids
+            // file copy).
+            ret = rewriteExif(buf, tags);
+        } catch (IOException e) {
+            closeSilently(file);
+            throw e;
+        } finally {
+            closeSilently(is);
+        }
+        file.close();
+        return ret;
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+     * the given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+     *            rewrite.
+     * @param tags tags that will be written into the jpeg ByteBuffer over
+     *            existing tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the ByteBuffer.
+     * @throws IOException
+     */
+    public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+        ExifModifier mod = null;
+        try {
+            mod = new ExifModifier(buf, this);
+            for (ExifTag t : tags) {
+                mod.modifyTag(t);
+            }
+            return mod.commit();
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+     * fall back to overwriting file. This preserves tags that are not being
+     * rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException,
+            IOException {
+        // Attempt in-place write
+        if (!rewriteExif(filename, tags)) {
+            // Fall back to doing a copy
+            ExifData tempData = mData;
+            mData = new ExifData(DEFAULT_BYTE_ORDER);
+            FileInputStream is = null;
+            ByteArrayOutputStream bytes = null;
+            try {
+                is = new FileInputStream(filename);
+                bytes = new ByteArrayOutputStream();
+                doExifStreamIO(is, bytes);
+                byte[] imageBytes = bytes.toByteArray();
+                readExif(imageBytes);
+                setTags(tags);
+                writeExif(imageBytes, filename);
+            } catch (IOException e) {
+                closeSilently(is);
+                throw e;
+            } finally {
+                is.close();
+                // Prevent clobbering of mData
+                mData = tempData;
+            }
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata using the tags in
+     * this ExifInterface object. If this fails, fall back to overwriting file.
+     * This preserves tags that are not being rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+        forceRewriteExif(filename, getAllTags());
+    }
+
+    /**
+     * Get the exif tags in this ExifInterface object or null if none exist.
+     *
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getAllTags() {
+        return mData.getAllTags();
+    }
+
+    /**
+     * Returns a list of ExifTags that share a TID (which can be obtained by
+     * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param tagId a TID as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForTagId(short tagId) {
+        return mData.getAllTagsForTagId(tagId);
+    }
+
+    /**
+     * Returns a list of ExifTags that share an IFD (which can be obtained by
+     * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param ifdId an IFD as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForIfdId(int ifdId) {
+        return mData.getAllTagsForIfd(ifdId);
+    }
+
+    /**
+     * Gets an ExifTag for an IFD other than the tag's default.
+     *
+     * @see #getTag
+     */
+    public ExifTag getTag(int tagId, int ifdId) {
+        if (!ExifTag.isValidIfd(ifdId)) {
+            return null;
+        }
+        return mData.getTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Returns the ExifTag in that tag's default IFD for a defined tag constant
+     * or null if none exists.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return an {@link ExifTag} or null if none exists.
+     */
+    public ExifTag getTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTag(tagId, ifdId);
+    }
+
+    /**
+     * Gets a tag value for an IFD other than the tag's default.
+     *
+     * @see #getTagValue
+     */
+    public Object getTagValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        return (t == null) ? null : t.getValue();
+    }
+
+    /**
+     * Returns the value of the ExifTag in that tag's default IFD for a defined
+     * tag constant or null if none exists or the value could not be cast into
+     * the return type.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the value of the ExifTag or null if none exists.
+     */
+    public Object getTagValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagValue(tagId, ifdId);
+    }
+
+    /*
+     * Getter methods that are similar to getTagValue. Null is returned if the
+     * tag value cannot be cast into the return type.
+     */
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsString();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagStringValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId, int ifdId) {
+        long[] l = getTagLongValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Long(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId, int ifdId) {
+        int[] l = getTagIntValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Integer(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId, int ifdId) {
+        byte[] l = getTagByteValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Byte(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId, int ifdId) {
+        Rational[] l = getTagRationalValues(tagId, ifdId);
+        if (l == null || l.length == 0) {
+            return null;
+        }
+        return new Rational(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsLongs();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsInts();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsBytes();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsRationals();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValues(tagId, ifdId);
+    }
+
+    /**
+     * Checks whether a tag has a defined number of elements.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return true if the tag has a defined number of elements.
+     */
+    public boolean isTagCountDefined(int tagId) {
+        int info = getTagInfo().get(tagId);
+        // No value in info can be zero, as all tags have a non-zero type
+        if (info == 0) {
+            return false;
+        }
+        return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+    }
+
+    /**
+     * Gets the defined number of elements for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+     *         tag or the number of elements is not defined.
+     */
+    public int getDefinedTagCount(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return ExifTag.SIZE_UNDEFINED;
+        }
+        return getComponentCountFromInfo(info);
+    }
+
+    /**
+     * Gets the number of elements for an ExifTag in a given IFD.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD containing the ExifTag to check.
+     * @return the number of elements in the ExifTag, if the tag's size is
+     *         undefined this will return the actual number of elements that is
+     *         in the ExifTag's value.
+     */
+    public int getActualTagCount(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return 0;
+        }
+        return t.getComponentCount();
+    }
+
+    /**
+     * Gets the default IFD for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+     *         definition exists.
+     */
+    public int getDefinedTagDefaultIfd(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == DEFINITION_NULL) {
+            return IFD_NULL;
+        }
+        return getTrueIfd(tagId);
+    }
+
+    /**
+     * Gets the defined type for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the type.
+     * @see ExifTag#getDataType()
+     */
+    public short getDefinedTagType(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return -1;
+        }
+        return getTypeFromInfo(info);
+    }
+
+    /**
+     * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+     * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+     * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+     * <p>
+     * Note: defining tags with these TID's is disallowed.
+     *
+     * @param tag a tag's TID (can be obtained from a defined tag constant with
+     *            {@link #getTrueTagKey}).
+     * @return true if the TID is that of an offset tag.
+     */
+    protected static boolean isOffsetTag(short tag) {
+        return sOffsetTags.contains(tag);
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in a given IFD if that IFD is
+     * allowed for the tag.  This method will fail anytime the appropriate
+     * {@link ExifTag#setValue} for this tag's datatype would fail.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the tag should be in.
+     * @param val the value of the tag to set.
+     * @return an ExifTag object or null if one could not be constructed.
+     * @see #buildTag
+     */
+    public ExifTag buildTag(int tagId, int ifdId, Object val) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0 || val == null) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+            return null;
+        }
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        if (!t.setValue(val)) {
+            return null;
+        }
+        return t;
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in the tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the tag's value.
+     * @return an ExifTag object.
+     */
+    public ExifTag buildTag(int tagId, Object val) {
+        int ifdId = getTrueIfd(tagId);
+        return buildTag(tagId, ifdId, val);
+    }
+
+    protected ExifTag buildUninitializedTag(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        int ifdId = getTrueIfd(tagId);
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        return t;
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists in the given IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the ExifTag is in.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     * @see #setTagValue
+     */
+    public boolean setTagValue(int tagId, int ifdId, Object val) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return false;
+        }
+        return t.setValue(val);
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists it's default IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     */
+    public boolean setTagValue(int tagId, Object val) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return setTagValue(tagId, ifdId, val);
+    }
+
+    /**
+     * Puts an ExifTag into this ExifInterface object's tags, removing a
+     * previous ExifTag with the same TID and IFD. The IFD it is put into will
+     * be the one the tag was created with in {@link #buildTag}.
+     *
+     * @param tag an ExifTag to put into this ExifInterface's tags.
+     * @return the previous ExifTag with the same TID and IFD or null if none
+     *         exists.
+     */
+    public ExifTag setTag(ExifTag tag) {
+        return mData.addTag(tag);
+    }
+
+    /**
+     * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+     * previous ExifTags with the same TID and IFDs will be removed.
+     *
+     * @param tags a Collection of ExifTags.
+     * @see #setTag
+     */
+    public void setTags(Collection<ExifTag> tags) {
+        for (ExifTag t : tags) {
+            setTag(t);
+        }
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from the given IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD of the ExifTag to remove.
+     */
+    public void deleteTag(int tagId, int ifdId) {
+        mData.removeTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from that tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void deleteTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        deleteTag(tagId, ifdId);
+    }
+
+    /**
+     * Creates a new tag definition in this ExifInterface object for a given TID
+     * and default IFD. Creating a definition with the same TID and default IFD
+     * as a previous definition will override it.
+     *
+     * @param tagId the TID for the tag.
+     * @param defaultIfd the default IFD for the tag.
+     * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+     * @param defaultComponentCount the number of elements of this tag's type in
+     *            the tags value.
+     * @param allowedIfds the IFD's this tag is allowed to be put in.
+     * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+     *         {@link #TAG_NULL} if the definition could not be made.
+     */
+    public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+            short defaultComponentCount, int[] allowedIfds) {
+        if (sBannedDefines.contains(tagId)) {
+            return TAG_NULL;
+        }
+        if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+            int tagDef = defineTag(defaultIfd, tagId);
+            if (tagDef == TAG_NULL) {
+                return TAG_NULL;
+            }
+            int[] otherDefs = getTagDefinitionsForTagId(tagId);
+            SparseIntArray infos = getTagInfo();
+            // Make sure defaultIfd is in allowedIfds
+            boolean defaultCheck = false;
+            for (int i : allowedIfds) {
+                if (defaultIfd == i) {
+                    defaultCheck = true;
+                }
+                if (!ExifTag.isValidIfd(i)) {
+                    return TAG_NULL;
+                }
+            }
+            if (!defaultCheck) {
+                return TAG_NULL;
+            }
+
+            int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+            // Make sure no identical tags can exist in allowedIfds
+            if (otherDefs != null) {
+                for (int def : otherDefs) {
+                    int tagInfo = infos.get(def);
+                    int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+                    if ((ifdFlags & allowedFlags) != 0) {
+                        return TAG_NULL;
+                    }
+                }
+            }
+            getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+            return tagDef;
+        }
+        return TAG_NULL;
+    }
+
+    protected int getTagDefinition(short tagId, int defaultIfd) {
+        return getTagInfo().get(defineTag(defaultIfd, tagId));
+    }
+
+    protected int[] getTagDefinitionsForTagId(short tagId) {
+        int[] ifds = IfdData.getIfds();
+        int[] defs = new int[ifds.length];
+        int counter = 0;
+        SparseIntArray infos = getTagInfo();
+        for (int i : ifds) {
+            int def = defineTag(i, tagId);
+            if (infos.get(def) != DEFINITION_NULL) {
+                defs[counter++] = def;
+            }
+        }
+        if (counter == 0) {
+            return null;
+        }
+
+        return Arrays.copyOfRange(defs, 0, counter);
+    }
+
+    protected int getTagDefinitionForTag(ExifTag tag) {
+        short type = tag.getDataType();
+        int count = tag.getComponentCount();
+        int ifd = tag.getIfd();
+        return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+    }
+
+    protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+        int[] defs = getTagDefinitionsForTagId(tagId);
+        if (defs == null) {
+            return TAG_NULL;
+        }
+        SparseIntArray infos = getTagInfo();
+        int ret = TAG_NULL;
+        for (int i : defs) {
+            int info = infos.get(i);
+            short def_type = getTypeFromInfo(info);
+            int def_count = getComponentCountFromInfo(info);
+            int[] def_ifds = getAllowedIfdsFromInfo(info);
+            boolean valid_ifd = false;
+            for (int j : def_ifds) {
+                if (j == ifd) {
+                    valid_ifd = true;
+                    break;
+                }
+            }
+            if (valid_ifd && type == def_type
+                    && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
+                ret = i;
+                break;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Removes a tag definition for given defined tag constant.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void removeTagDefinition(int tagId) {
+        getTagInfo().delete(tagId);
+    }
+
+    /**
+     * Resets tag definitions to the default ones.
+     */
+    public void resetTagDefinitions() {
+        mTagInfo = null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+     *
+     * @return the thumbnail as a bitmap.
+     */
+    public Bitmap getThumbnailBitmap() {
+        if (mData.hasCompressedThumbnail()) {
+            byte[] thumb = mData.getCompressedThumbnail();
+            return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement uncompressed
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+     * The bytes may either be an uncompressed strip as specified in the exif
+     * standard or a jpeg compressed image.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnailBytes() {
+        if (mData.hasCompressedThumbnail()) {
+            return mData.getCompressedThumbnail();
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement this
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnail() {
+        return mData.getCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail is compressed.
+     *
+     * @return true if the thumbnail is compressed.
+     */
+    public boolean isThumbnailCompressed() {
+        return mData.hasCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail exists.
+     *
+     * @return true if a compressed thumbnail exists.
+     */
+    public boolean hasThumbnail() {
+        // TODO: add back in uncompressed strip
+        return mData.hasCompressedThumbnail();
+    }
+
+    // TODO: uncompressed thumbnail setters
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a byte array containing a jpeg compressed image.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(byte[] thumb) {
+        mData.clearThumbnailAndStrips();
+        mData.setCompressedThumbnail(thumb);
+        return true;
+    }
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a bitmap to compress to a jpeg thumbnail.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(Bitmap thumb) {
+        ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+        if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+            return false;
+        }
+        return setCompressedThumbnail(thumbnail.toByteArray());
+    }
+
+    /**
+     * Clears the compressed thumbnail if it exists.
+     */
+    public void removeCompressedThumbnail() {
+        mData.setCompressedThumbnail(null);
+    }
+
+    // Convenience methods:
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    public String getUserComment() {
+        return mData.getUserComment();
+    }
+
+    /**
+     * Returns the Orientation ExifTag value for a given number of degrees.
+     *
+     * @param degrees the amount an image is rotated in degrees.
+     */
+    public static short getOrientationValueForRotation(int degrees) {
+        degrees %= 360;
+        if (degrees < 0) {
+            degrees += 360;
+        }
+        if (degrees < 90) {
+            return Orientation.TOP_LEFT; // 0 degrees
+        } else if (degrees < 180) {
+            return Orientation.RIGHT_TOP; // 90 degrees cw
+        } else if (degrees < 270) {
+            return Orientation.BOTTOM_LEFT; // 180 degrees
+        } else {
+            return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+        }
+    }
+
+    /**
+     * Returns the rotation degrees corresponding to an ExifTag Orientation
+     * value.
+     *
+     * @param orientation the ExifTag Orientation value.
+     */
+    public static int getRotationForOrientationValue(short orientation) {
+        switch (orientation) {
+            case Orientation.TOP_LEFT:
+                return 0;
+            case Orientation.RIGHT_TOP:
+                return 90;
+            case Orientation.BOTTOM_LEFT:
+                return 180;
+            case Orientation.RIGHT_BOTTOM:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Gets the double representation of the GPS latitude or longitude
+     * coordinate.
+     *
+     * @param coordinate an array of 3 Rationals representing the degrees,
+     *            minutes, and seconds of the GPS location as defined in the
+     *            exif specification.
+     * @param reference a GPS reference reperesented by a String containing "N",
+     *            "S", "E", or "W".
+     * @return the GPS coordinate represented as degrees + minutes/60 +
+     *         seconds/3600
+     */
+    public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+        try {
+            double degrees = coordinate[0].toDouble();
+            double minutes = coordinate[1].toDouble();
+            double seconds = coordinate[2].toDouble();
+            double result = degrees + minutes / 60.0 + seconds / 3600.0;
+            if ((reference.equals("S") || reference.equals("W"))) {
+                return -result;
+            }
+            return result;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Gets the GPS latitude and longitude as a pair of doubles from this
+     * ExifInterface object's tags, or null if the necessary tags do not exist.
+     *
+     * @return an array of 2 doubles containing the latitude, and longitude
+     *         respectively.
+     * @see #convertLatOrLongToDouble
+     */
+    public double[] getLatLongAsDoubles() {
+        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+        if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+                || latitude.length < 3 || longitude.length < 3) {
+            return null;
+        }
+        double[] latLon = new double[2];
+        latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+        latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+        return latLon;
+    }
+
+    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+    private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+    private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+    private final Calendar mGPSTimeStampCalendar = Calendar
+            .getInstance(TimeZone.getTimeZone("UTC"));
+
+    /**
+     * Creates, formats, and sets the DateTimeStamp tag for one of:
+     * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+     * {@link #TAG_DATE_TIME_ORIGINAL}.
+     *
+     * @param tagId one of the DateTimeStamp tags.
+     * @param timestamp a timestamp to format.
+     * @param timezone a TimeZone object.
+     * @return true if success, false if the tag could not be set.
+     */
+    public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+        if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+                || tagId == TAG_DATE_TIME_ORIGINAL) {
+            mDateTimeStampFormat.setTimeZone(timezone);
+            ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+            if (t == null) {
+                return false;
+            }
+            setTag(t);
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Creates and sets all to the GPS tags for a give latitude and longitude.
+     *
+     * @param latitude a GPS latitude coordinate.
+     * @param longitude a GPS longitude coordinate.
+     * @return true if success, false if they could not be created or set.
+     */
+    public boolean addGpsTags(double latitude, double longitude) {
+        ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+        ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+        ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+                latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
+                        : ExifInterface.GpsLatitudeRef.SOUTH);
+        ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+                longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
+                        : ExifInterface.GpsLongitudeRef.WEST);
+        if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+            return false;
+        }
+        setTag(latTag);
+        setTag(longTag);
+        setTag(latRefTag);
+        setTag(longRefTag);
+        return true;
+    }
+
+    /**
+     * Creates and sets the GPS timestamp tag.
+     *
+     * @param timestamp a GPS timestamp.
+     * @return true if success, false if could not be created or set.
+     */
+    public boolean addGpsDateTimeStampTag(long timestamp) {
+        ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+        t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+                new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+        });
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        return true;
+    }
+
+    private static Rational[] toExifLatLong(double value) {
+        // convert to the format dd/1 mm/1 ssss/100
+        value = Math.abs(value);
+        int degrees = (int) value;
+        value = (value - degrees) * 60;
+        int minutes = (int) value;
+        value = (value - minutes) * 6000;
+        int seconds = (int) value;
+        return new Rational[] {
+                new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+        };
+    }
+
+    private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+        byte[] buf = new byte[1024];
+        int ret = is.read(buf, 0, 1024);
+        while (ret != -1) {
+            os.write(buf, 0, ret);
+            ret = is.read(buf, 0, 1024);
+        }
+    }
+
+    protected static void closeSilently(Closeable c) {
+        if (c != null) {
+            try {
+                c.close();
+            } catch (Throwable e) {
+                // ignored
+            }
+        }
+    }
+
+    private SparseIntArray mTagInfo = null;
+
+    protected SparseIntArray getTagInfo() {
+        if (mTagInfo == null) {
+            mTagInfo = new SparseIntArray();
+            initTagInfo();
+        }
+        return mTagInfo;
+    }
+
+    private void initTagInfo() {
+        /**
+         * We put tag information in a 4-bytes integer. The first byte a bitmask
+         * representing the allowed IFDs of the tag, the second byte is the data
+         * type, and the last two byte are a short value indicating the default
+         * component count of this tag.
+         */
+        // IFD0 tags
+        int[] ifdAllowedIfds = {
+                IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+        };
+        int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+                | 1);
+        mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+        mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MODEL,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ARTIST,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // IFD1 tags
+        int[] ifd1AllowedIfds = {
+            IfdId.TYPE_IFD_1
+        };
+        int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // Exif tags
+        int[] exifAllowedIfds = {
+            IfdId.TYPE_IFD_EXIF
+        };
+        int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_OECF,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FLASH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CONTRAST,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SATURATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+                | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // GPS tag
+        int[] gpsAllowedIfds = {
+            IfdId.TYPE_IFD_GPS
+        };
+        int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+        mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+        // Interoperability tag
+        int[] interopAllowedIfds = {
+            IfdId.TYPE_IFD_INTEROPERABILITY
+        };
+        int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+        mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+                | ExifTag.SIZE_UNDEFINED);
+    }
+
+    protected static int getAllowedIfdFlagsFromInfo(int info) {
+        return info >>> 24;
+    }
+
+    protected static int[] getAllowedIfdsFromInfo(int info) {
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        int[] ifds = IfdData.getIfds();
+        ArrayList<Integer> l = new ArrayList<Integer>();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            int flag = (ifdFlags >> i) & 1;
+            if (flag == 1) {
+                l.add(ifds[i]);
+            }
+        }
+        if (l.size() <= 0) {
+            return null;
+        }
+        int[] ret = new int[l.size()];
+        int j = 0;
+        for (int i : l) {
+            ret[j++] = i;
+        }
+        return ret;
+    }
+
+    protected static boolean isIfdAllowed(int info, int ifd) {
+        int[] ifds = IfdData.getIfds();
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        for (int i = 0; i < ifds.length; i++) {
+            if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+        if (allowedIfds == null || allowedIfds.length == 0) {
+            return 0;
+        }
+        int flags = 0;
+        int[] ifds = IfdData.getIfds();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            for (int j : allowedIfds) {
+                if (ifds[i] == j) {
+                    flags |= 1 << i;
+                    break;
+                }
+            }
+        }
+        return flags;
+    }
+
+    protected static short getTypeFromInfo(int info) {
+        return (short) ((info >> 16) & 0x0ff);
+    }
+
+    protected static int getComponentCountFromInfo(int info) {
+        return info & 0x0ffff;
+    }
+
+}
diff --git a/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
new file mode 100644
index 0000000..bf923ec
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * 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.exif;
+
+public class ExifInvalidFormatException extends Exception {
+    public ExifInvalidFormatException(String meg) {
+        super(meg);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifModifier.java b/src/com/android/gallery3d/exif/ExifModifier.java
new file mode 100644
index 0000000..f00362b
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifModifier.java
@@ -0,0 +1,196 @@
+/*
+ * 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.exif;
+
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+class ExifModifier {
+    public static final String TAG = "ExifModifier";
+    public static final boolean DEBUG = false;
+    private final ByteBuffer mByteBuffer;
+    private final ExifData mTagToModified;
+    private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+    private final ExifInterface mInterface;
+    private int mOffsetBase;
+
+    private static class TagOffset {
+        final int mOffset;
+        final ExifTag mTag;
+
+        TagOffset(ExifTag tag, int offset) {
+            mTag = tag;
+            mOffset = offset;
+        }
+    }
+
+    protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+            ExifInvalidFormatException {
+        mByteBuffer = byteBuffer;
+        mOffsetBase = byteBuffer.position();
+        mInterface = iRef;
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(byteBuffer);
+            // Do not require any IFD;
+            ExifParser parser = ExifParser.parse(is, mInterface);
+            mTagToModified = new ExifData(parser.getByteOrder());
+            mOffsetBase += parser.getTiffStartPosition();
+            mByteBuffer.position(0);
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+    }
+
+    protected ByteOrder getByteOrder() {
+        return mTagToModified.getByteOrder();
+    }
+
+    protected boolean commit() throws IOException, ExifInvalidFormatException {
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(mByteBuffer);
+            int flag = 0;
+            IfdData[] ifdDatas = new IfdData[] {
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
+            };
+
+            if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+                flag |= ExifParser.OPTION_IFD_0;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+                flag |= ExifParser.OPTION_IFD_1;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+                flag |= ExifParser.OPTION_IFD_EXIF;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+                flag |= ExifParser.OPTION_IFD_GPS;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
+                flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
+            }
+
+            ExifParser parser = ExifParser.parse(is, flag, mInterface);
+            int event = parser.next();
+            IfdData currIfd = null;
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        currIfd = ifdDatas[parser.getCurrentIfd()];
+                        if (currIfd == null) {
+                            parser.skipRemainingTagsInCurrentIfd();
+                        }
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag oldTag = parser.getTag();
+                        ExifTag newTag = currIfd.getTag(oldTag.getTagId());
+                        if (newTag != null) {
+                            if (newTag.getComponentCount() != oldTag.getComponentCount()
+                                    || newTag.getDataType() != oldTag.getDataType()) {
+                                return false;
+                            } else {
+                                mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
+                                currIfd.removeTag(oldTag.getTagId());
+                                if (currIfd.getTagCount() == 0) {
+                                    parser.skipRemainingTagsInCurrentIfd();
+                                }
+                            }
+                        }
+                        break;
+                }
+                event = parser.next();
+            }
+            for (IfdData ifd : ifdDatas) {
+                if (ifd != null && ifd.getTagCount() > 0) {
+                    return false;
+                }
+            }
+            modify();
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+        return true;
+    }
+
+    private void modify() {
+        mByteBuffer.order(getByteOrder());
+        for (TagOffset tagOffset : mTagOffsets) {
+            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
+        }
+    }
+
+    private void writeTagValue(ExifTag tag, int offset) {
+        if (DEBUG) {
+            Log.v(TAG, "modifying tag to: \n" + tag.toString());
+            Log.v(TAG, "at offset: " + offset);
+        }
+        mByteBuffer.position(offset + mOffsetBase);
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    mByteBuffer.put(buf);
+                } else {
+                    mByteBuffer.put(buf);
+                    mByteBuffer.put((byte) 0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    Rational v = tag.getRational(i);
+                    mByteBuffer.putInt((int) v.getNumerator());
+                    mByteBuffer.putInt((int) v.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                mByteBuffer.put(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+
+    public void modifyTag(ExifTag tag) {
+        mTagToModified.addTag(tag);
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java
new file mode 100644
index 0000000..7ca05f2
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -0,0 +1,518 @@
+/*
+ * 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.exif;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * This class provides a way to replace the Exif header of a JPEG image.
+ * <p>
+ * Below is an example of writing EXIF data into a file
+ *
+ * <pre>
+ * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
+ *     OutputStream os = null;
+ *     try {
+ *         os = new FileOutputStream(path);
+ *         ExifOutputStream eos = new ExifOutputStream(os);
+ *         // Set the exif header
+ *         eos.setExifData(exif);
+ *         // Write the original jpeg out, the header will be add into the file.
+ *         eos.write(jpeg);
+ *     } catch (FileNotFoundException e) {
+ *         e.printStackTrace();
+ *     } catch (IOException e) {
+ *         e.printStackTrace();
+ *     } finally {
+ *         if (os != null) {
+ *             try {
+ *                 os.close();
+ *             } catch (IOException e) {
+ *                 e.printStackTrace();
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+class ExifOutputStream extends FilterOutputStream {
+    private static final String TAG = "ExifOutputStream";
+    private static final boolean DEBUG = false;
+    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+    private static final int STATE_SOI = 0;
+    private static final int STATE_FRAME_HEADER = 1;
+    private static final int STATE_JPEG_DATA = 2;
+
+    private static final int EXIF_HEADER = 0x45786966;
+    private static final short TIFF_HEADER = 0x002A;
+    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+    private static final short TAG_SIZE = 12;
+    private static final short TIFF_HEADER_SIZE = 8;
+    private static final int MAX_EXIF_SIZE = 65535;
+
+    private ExifData mExifData;
+    private int mState = STATE_SOI;
+    private int mByteToSkip;
+    private int mByteToCopy;
+    private byte[] mSingleByteArray = new byte[1];
+    private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+    private final ExifInterface mInterface;
+
+    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
+        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+        mInterface = iRef;
+    }
+
+    /**
+     * Sets the ExifData to be written into the JPEG file. Should be called
+     * before writing image data.
+     */
+    protected void setExifData(ExifData exifData) {
+        mExifData = exifData;
+    }
+
+    /**
+     * Gets the Exif header to be written into the JPEF file.
+     */
+    protected ExifData getExifData() {
+        return mExifData;
+    }
+
+    private int requestByteToBuffer(int requestByteCount, byte[] buffer
+            , int offset, int length) {
+        int byteNeeded = requestByteCount - mBuffer.position();
+        int byteToRead = length > byteNeeded ? byteNeeded : length;
+        mBuffer.put(buffer, offset, byteToRead);
+        return byteToRead;
+    }
+
+    /**
+     * Writes the image out. The input data should be a valid JPEG format. After
+     * writing, it's Exif header will be replaced by the given header.
+     */
+    @Override
+    public void write(byte[] buffer, int offset, int length) throws IOException {
+        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+                && length > 0) {
+            if (mByteToSkip > 0) {
+                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+                length -= byteToProcess;
+                mByteToSkip -= byteToProcess;
+                offset += byteToProcess;
+            }
+            if (mByteToCopy > 0) {
+                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+                out.write(buffer, offset, byteToProcess);
+                length -= byteToProcess;
+                mByteToCopy -= byteToProcess;
+                offset += byteToProcess;
+            }
+            if (length == 0) {
+                return;
+            }
+            switch (mState) {
+                case STATE_SOI:
+                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    if (mBuffer.position() < 2) {
+                        return;
+                    }
+                    mBuffer.rewind();
+                    if (mBuffer.getShort() != JpegHeader.SOI) {
+                        throw new IOException("Not a valid jpeg image, cannot write exif");
+                    }
+                    out.write(mBuffer.array(), 0, 2);
+                    mState = STATE_FRAME_HEADER;
+                    mBuffer.rewind();
+                    writeExifData();
+                    break;
+                case STATE_FRAME_HEADER:
+                    // We ignore the APP1 segment and copy all other segments
+                    // until SOF tag.
+                    byteRead = requestByteToBuffer(4, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    // Check if this image data doesn't contain SOF.
+                    if (mBuffer.position() == 2) {
+                        short tag = mBuffer.getShort();
+                        if (tag == JpegHeader.EOI) {
+                            out.write(mBuffer.array(), 0, 2);
+                            mBuffer.rewind();
+                        }
+                    }
+                    if (mBuffer.position() < 4) {
+                        return;
+                    }
+                    mBuffer.rewind();
+                    short marker = mBuffer.getShort();
+                    if (marker == JpegHeader.APP1) {
+                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
+                        mState = STATE_JPEG_DATA;
+                    } else if (!JpegHeader.isSofMarker(marker)) {
+                        out.write(mBuffer.array(), 0, 4);
+                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+                    } else {
+                        out.write(mBuffer.array(), 0, 4);
+                        mState = STATE_JPEG_DATA;
+                    }
+                    mBuffer.rewind();
+            }
+        }
+        if (length > 0) {
+            out.write(buffer, offset, length);
+        }
+    }
+
+    /**
+     * Writes the one bytes out. The input data should be a valid JPEG format.
+     * After writing, it's Exif header will be replaced by the given header.
+     */
+    @Override
+    public void write(int oneByte) throws IOException {
+        mSingleByteArray[0] = (byte) (0xff & oneByte);
+        write(mSingleByteArray);
+    }
+
+    /**
+     * Equivalent to calling write(buffer, 0, buffer.length).
+     */
+    @Override
+    public void write(byte[] buffer) throws IOException {
+        write(buffer, 0, buffer.length);
+    }
+
+    private void writeExifData() throws IOException {
+        if (mExifData == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.v(TAG, "Writing exif data...");
+        }
+        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
+        createRequiredIfdAndTag();
+        int exifSize = calculateAllOffset();
+        if (exifSize + 8 > MAX_EXIF_SIZE) {
+            throw new IOException("Exif header is too large (>64Kb)");
+        }
+        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+        dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+        dataOutputStream.writeShort(JpegHeader.APP1);
+        dataOutputStream.writeShort((short) (exifSize + 8));
+        dataOutputStream.writeInt(EXIF_HEADER);
+        dataOutputStream.writeShort((short) 0x0000);
+        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+        } else {
+            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+        }
+        dataOutputStream.setByteOrder(mExifData.getByteOrder());
+        dataOutputStream.writeShort(TIFF_HEADER);
+        dataOutputStream.writeInt(8);
+        writeAllTags(dataOutputStream);
+        writeThumbnail(dataOutputStream);
+        for (ExifTag t : nullTags) {
+            mExifData.addTag(t);
+        }
+    }
+
+    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
+        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
+        for(ExifTag t : data.getAllTags()) {
+            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
+                data.removeTag(t.getTagId(), t.getIfd());
+                nullTags.add(t);
+            }
+        }
+        return nullTags;
+    }
+
+    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+        if (mExifData.hasCompressedThumbnail()) {
+            dataOutputStream.write(mExifData.getCompressedThumbnail());
+        } else if (mExifData.hasUncompressedStrip()) {
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                dataOutputStream.write(mExifData.getStrip(i));
+            }
+        }
+    }
+
+    private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interoperabilityIfd != null) {
+            writeIfd(interoperabilityIfd, dataOutputStream);
+        }
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            writeIfd(gpsIfd, dataOutputStream);
+        }
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+        }
+    }
+
+    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        ExifTag[] tags = ifd.getAllTags();
+        dataOutputStream.writeShort((short) tags.length);
+        for (ExifTag tag : tags) {
+            dataOutputStream.writeShort(tag.getTagId());
+            dataOutputStream.writeShort(tag.getDataType());
+            dataOutputStream.writeInt(tag.getComponentCount());
+            if (DEBUG) {
+                Log.v(TAG, "\n" + tag.toString());
+            }
+            if (tag.getDataSize() > 4) {
+                dataOutputStream.writeInt(tag.getOffset());
+            } else {
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
+                for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+                    dataOutputStream.write(0);
+                }
+            }
+        }
+        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+        for (ExifTag tag : tags) {
+            if (tag.getDataSize() > 4) {
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
+            }
+        }
+    }
+
+    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+        ExifTag[] tags = ifd.getAllTags();
+        for (ExifTag tag : tags) {
+            if (tag.getDataSize() > 4) {
+                tag.setOffset(offset);
+                offset += tag.getDataSize();
+            }
+        }
+        return offset;
+    }
+
+    private void createRequiredIfdAndTag() throws IOException {
+        // IFD0 is required for all file
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        if (ifd0 == null) {
+            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+            mExifData.addIfdData(ifd0);
+        }
+        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+        if (exifOffsetTag == null) {
+            throw new IOException("No definition for crucial exif tag: "
+                    + ExifInterface.TAG_EXIF_IFD);
+        }
+        ifd0.setTag(exifOffsetTag);
+
+        // Exif IFD is required for all files.
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        if (exifIfd == null) {
+            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+            mExifData.addIfdData(exifIfd);
+        }
+
+        // GPS IFD
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+            if (gpsOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_GPS_IFD);
+            }
+            ifd0.setTag(gpsOffsetTag);
+        }
+
+        // Interoperability IFD
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            ExifTag interOffsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+            if (interOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
+            }
+            exifIfd.setTag(interOffsetTag);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+
+            ExifTag offsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            }
+
+            ifd1.setTag(offsetTag);
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            }
+
+            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+            ifd1.setTag(lengthTag);
+
+            // Get rid of tags for uncompressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+        } else if (mExifData.hasUncompressedStrip()) {
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+            int stripCount = mExifData.getStripCount();
+            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_OFFSETS);
+            }
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            }
+            long[] lengths = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                lengths[i] = mExifData.getStrip(i).length;
+            }
+            lengthTag.setValue(lengths);
+            ifd1.setTag(offsetTag);
+            ifd1.setTag(lengthTag);
+            // Get rid of tags for compressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        } else if (ifd1 != null) {
+            // Get rid of offset and length tags if there is no thumbnail.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        }
+    }
+
+    private int calculateAllOffset() {
+        int offset = TIFF_HEADER_SIZE;
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        offset = calculateOffsetOfIfd(ifd0, offset);
+        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
+
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        offset = calculateOffsetOfIfd(exifIfd, offset);
+
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+                    .setValue(offset);
+            offset = calculateOffsetOfIfd(interIfd, offset);
+        }
+
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
+            offset = calculateOffsetOfIfd(gpsIfd, offset);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            ifd0.setOffsetToNextIfd(offset);
+            offset = calculateOffsetOfIfd(ifd1, offset);
+        }
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+                    .setValue(offset);
+            offset += mExifData.getCompressedThumbnail().length;
+        } else if (mExifData.hasUncompressedStrip()) {
+            int stripCount = mExifData.getStripCount();
+            long[] offsets = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                offsets[i] = offset;
+                offset += mExifData.getStrip(i).length;
+            }
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+                    offsets);
+        }
+        return offset;
+    }
+
+    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    dataOutputStream.write(buf);
+                } else {
+                    dataOutputStream.write(buf);
+                    dataOutputStream.write(0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeRational(tag.getRational(i));
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                dataOutputStream.write(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java
new file mode 100644
index 0000000..5467d42
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifParser.java
@@ -0,0 +1,916 @@
+/*
+ * 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.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format
+ * InputStream, the caller can request which IFD's to read via
+ * {@link #parse(InputStream, int)} with given options.
+ * <p>
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
+ * <pre>
+ * void parse() {
+ *     ExifParser parser = ExifParser.parse(mImageInputStream,
+ *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ *     int event = parser.next();
+ *     while (event != ExifParser.EVENT_END) {
+ *         switch (event) {
+ *             case ExifParser.EVENT_START_OF_IFD:
+ *                 break;
+ *             case ExifParser.EVENT_NEW_TAG:
+ *                 ExifTag tag = parser.getTag();
+ *                 if (!tag.hasValue()) {
+ *                     parser.registerForTagValue(tag);
+ *                 } else {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ *                 tag = parser.getTag();
+ *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *         }
+ *         event = parser.next();
+ *     }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ *     // process the tag as you like.
+ * }
+ * </pre>
+ */
+class ExifParser {
+    private static final boolean LOGV = false;
+    private static final String TAG = "ExifParser";
+    /**
+     * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+     * know which IFD we are in.
+     */
+    public static final int EVENT_START_OF_IFD = 0;
+    /**
+     * When the parser reaches a new tag. Call {@link #getTag()}to get the
+     * corresponding tag.
+     */
+    public static final int EVENT_NEW_TAG = 1;
+    /**
+     * When the parser reaches the value area of tag that is registered by
+     * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+     * to get the corresponding tag.
+     */
+    public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+
+    /**
+     * When the parser reaches the compressed image area.
+     */
+    public static final int EVENT_COMPRESSED_IMAGE = 3;
+    /**
+     * When the parser reaches the uncompressed image strip. Call
+     * {@link #getStripIndex()} to get the index of the strip.
+     *
+     * @see #getStripIndex()
+     * @see #getStripCount()
+     */
+    public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+    /**
+     * When there is nothing more to parse.
+     */
+    public static final int EVENT_END = 5;
+
+    /**
+     * Option bit to request to parse IFD0.
+     */
+    public static final int OPTION_IFD_0 = 1 << 0;
+    /**
+     * Option bit to request to parse IFD1.
+     */
+    public static final int OPTION_IFD_1 = 1 << 1;
+    /**
+     * Option bit to request to parse Exif-IFD.
+     */
+    public static final int OPTION_IFD_EXIF = 1 << 2;
+    /**
+     * Option bit to request to parse GPS-IFD.
+     */
+    public static final int OPTION_IFD_GPS = 1 << 3;
+    /**
+     * Option bit to request to parse Interoperability-IFD.
+     */
+    public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+    /**
+     * Option bit to request to parse thumbnail.
+     */
+    public static final int OPTION_THUMBNAIL = 1 << 5;
+
+    protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+    protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+    // TIFF header
+    protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+    protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+    protected static final short TIFF_HEADER_TAIL = 0x002A;
+
+    protected static final int TAG_SIZE = 12;
+    protected static final int OFFSET_SIZE = 2;
+
+    private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+    protected static final int DEFAULT_IFD0_OFFSET = 8;
+
+    private final CountedDataInputStream mTiffStream;
+    private final int mOptions;
+    private int mIfdStartOffset = 0;
+    private int mNumOfTagInIfd = 0;
+    private int mIfdType;
+    private ExifTag mTag;
+    private ImageEvent mImageEvent;
+    private int mStripCount;
+    private ExifTag mStripSizeTag;
+    private ExifTag mJpegSizeTag;
+    private boolean mNeedToParseOffsetsInCurrentIfd;
+    private boolean mContainExifData = false;
+    private int mApp1End;
+    private int mOffsetToApp1EndFromSOF = 0;
+    private byte[] mDataAboveIfd0;
+    private int mIfd0Position;
+    private int mTiffStartPosition;
+    private final ExifInterface mInterface;
+
+    private static final short TAG_EXIF_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+    private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+    private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+    private static final short TAG_STRIP_OFFSETS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+    private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+
+    private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
+
+    private boolean isIfdRequested(int ifdType) {
+        switch (ifdType) {
+            case IfdId.TYPE_IFD_0:
+                return (mOptions & OPTION_IFD_0) != 0;
+            case IfdId.TYPE_IFD_1:
+                return (mOptions & OPTION_IFD_1) != 0;
+            case IfdId.TYPE_IFD_EXIF:
+                return (mOptions & OPTION_IFD_EXIF) != 0;
+            case IfdId.TYPE_IFD_GPS:
+                return (mOptions & OPTION_IFD_GPS) != 0;
+            case IfdId.TYPE_IFD_INTEROPERABILITY:
+                return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+        }
+        return false;
+    }
+
+    private boolean isThumbnailRequested() {
+        return (mOptions & OPTION_THUMBNAIL) != 0;
+    }
+
+    private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        if (inputStream == null) {
+            throw new IOException("Null argument inputStream to ExifParser");
+        }
+        if (LOGV) {
+            Log.v(TAG, "Reading exif...");
+        }
+        mInterface = iRef;
+        mContainExifData = seekTiffData(inputStream);
+        mTiffStream = new CountedDataInputStream(inputStream);
+        mOptions = options;
+        if (!mContainExifData) {
+            return;
+        }
+
+        parseTiffHeader();
+        long offset = mTiffStream.readUnsignedInt();
+        if (offset > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException("Invalid offset " + offset);
+        }
+        mIfd0Position = (int) offset;
+        mIfdType = IfdId.TYPE_IFD_0;
+        if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
+            registerIfd(IfdId.TYPE_IFD_0, offset);
+            if (offset != DEFAULT_IFD0_OFFSET) {
+                mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+                read(mDataAboveIfd0);
+            }
+        }
+    }
+
+    /**
+     * Parses the the given InputStream with the given options
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     */
+    protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, options, iRef);
+    }
+
+    /**
+     * Parses the the given InputStream with default options; that is, every IFD
+     * and thumbnaill will be parsed.
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #parse(InputStream, int)
+     */
+    protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+                | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+                | OPTION_THUMBNAIL, iRef);
+    }
+
+    /**
+     * Moves the parser forward and returns the next parsing event
+     *
+     * @exception IOException
+     * @exception ExifInvalidFormatException
+     * @see #EVENT_START_OF_IFD
+     * @see #EVENT_NEW_TAG
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     * @see #EVENT_COMPRESSED_IMAGE
+     * @see #EVENT_UNCOMPRESSED_STRIP
+     * @see #EVENT_END
+     */
+    protected int next() throws IOException, ExifInvalidFormatException {
+        if (!mContainExifData) {
+            return EVENT_END;
+        }
+        int offset = mTiffStream.getReadByteCount();
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        if (offset < endOfTags) {
+            mTag = readTag();
+            if (mTag == null) {
+                return next();
+            }
+            if (mNeedToParseOffsetsInCurrentIfd) {
+                checkOffsetOrImageTag(mTag);
+            }
+            return EVENT_NEW_TAG;
+        } else if (offset == endOfTags) {
+            // There is a link to ifd1 at the end of ifd0
+            if (mIfdType == IfdId.TYPE_IFD_0) {
+                long ifdOffset = readUnsignedLong();
+                if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+                    if (ifdOffset != 0) {
+                        registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+                    }
+                }
+            } else {
+                int offsetSize = 4;
+                // Some camera models use invalid length of the offset
+                if (mCorrespondingEvent.size() > 0) {
+                    offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+                            mTiffStream.getReadByteCount();
+                }
+                if (offsetSize < 4) {
+                    Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+                } else {
+                    long ifdOffset = readUnsignedLong();
+                    if (ifdOffset != 0) {
+                        Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+                    }
+                }
+            }
+        }
+        while (mCorrespondingEvent.size() != 0) {
+            Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+            Object event = entry.getValue();
+            try {
+                skipTo(entry.getKey());
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+                        " for " + event.getClass().getName() + ", the file may be broken.");
+                continue;
+            }
+            if (event instanceof IfdEvent) {
+                mIfdType = ((IfdEvent) event).ifd;
+                mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+                mIfdStartOffset = entry.getKey();
+
+                if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+                    Log.w(TAG, "Invalid size of IFD " + mIfdType);
+                    return EVENT_END;
+                }
+
+                mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+                if (((IfdEvent) event).isRequested) {
+                    return EVENT_START_OF_IFD;
+                } else {
+                    skipRemainingTagsInCurrentIfd();
+                }
+            } else if (event instanceof ImageEvent) {
+                mImageEvent = (ImageEvent) event;
+                return mImageEvent.type;
+            } else {
+                ExifTagEvent tagEvent = (ExifTagEvent) event;
+                mTag = tagEvent.tag;
+                if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+                    readFullTagValue(mTag);
+                    checkOffsetOrImageTag(mTag);
+                }
+                if (tagEvent.isRequested) {
+                    return EVENT_VALUE_OF_REGISTERED_TAG;
+                }
+            }
+        }
+        return EVENT_END;
+    }
+
+    /**
+     * Skips the tags area of current IFD, if the parser is not in the tag area,
+     * nothing will happen.
+     *
+     * @throws IOException
+     * @throws ExifInvalidFormatException
+     */
+    protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+        int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+        int offset = mTiffStream.getReadByteCount();
+        if (offset > endOfTags) {
+            return;
+        }
+        if (mNeedToParseOffsetsInCurrentIfd) {
+            while (offset < endOfTags) {
+                mTag = readTag();
+                offset += TAG_SIZE;
+                if (mTag == null) {
+                    continue;
+                }
+                checkOffsetOrImageTag(mTag);
+            }
+        } else {
+            skipTo(endOfTags);
+        }
+        long ifdOffset = readUnsignedLong();
+        // For ifd0, there is a link to ifd1 in the end of all tags
+        if (mIfdType == IfdId.TYPE_IFD_0
+                && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+            if (ifdOffset > 0) {
+                registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+            }
+        }
+    }
+
+    private boolean needToParseOffsetsInCurrentIfd() {
+        switch (mIfdType) {
+            case IfdId.TYPE_IFD_0:
+                return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+                        || isIfdRequested(IfdId.TYPE_IFD_1);
+            case IfdId.TYPE_IFD_1:
+                return isThumbnailRequested();
+            case IfdId.TYPE_IFD_EXIF:
+                // The offset to interoperability IFD is located in Exif IFD
+                return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+     * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+     * corresponding tag.
+     * <p>
+     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+     * of the value is greater than 4 bytes. One should call
+     * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+     * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+     * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * pointed by the offset.
+     * <p>
+     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+     * tag will have already been read except for tags of undefined type. For
+     * tags of undefined type, call one of the read methods to get the value.
+     *
+     * @see #registerForTagValue(ExifTag)
+     * @see #read(byte[])
+     * @see #read(byte[], int, int)
+     * @see #readLong()
+     * @see #readRational()
+     * @see #readString(int)
+     * @see #readString(int, Charset)
+     */
+    protected ExifTag getTag() {
+        return mTag;
+    }
+
+    /**
+     * Gets number of tags in the current IFD area.
+     */
+    protected int getTagCountInCurrentIfd() {
+        return mNumOfTagInIfd;
+    }
+
+    /**
+     * Gets the ID of current IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     * @see IfdId#TYPE_IFD_EXIF
+     */
+    protected int getCurrentIfd() {
+        return mIfdType;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the index of this strip.
+     *
+     * @see #getStripCount()
+     */
+    protected int getStripIndex() {
+        return mImageEvent.stripIndex;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the number of strip data.
+     *
+     * @see #getStripIndex()
+     */
+    protected int getStripCount() {
+        return mStripCount;
+    }
+
+    /**
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the strip size.
+     */
+    protected int getStripSize() {
+        if (mStripSizeTag == null)
+            return 0;
+        return (int) mStripSizeTag.getValueAt(0);
+    }
+
+    /**
+     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+     * the image data size.
+     */
+    protected int getCompressedImageSize() {
+        if (mJpegSizeTag == null) {
+            return 0;
+        }
+        return (int) mJpegSizeTag.getValueAt(0);
+    }
+
+    private void skipTo(int offset) throws IOException {
+        mTiffStream.skipTo(offset);
+        while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+            mCorrespondingEvent.pollFirstEntry();
+        }
+    }
+
+    /**
+     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+     * not contain the value if the size of the value is greater than 4 bytes.
+     * When the value is not available here, call this method so that the parser
+     * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * where the value is located.
+     *
+     * @see #EVENT_VALUE_OF_REGISTERED_TAG
+     */
+    protected void registerForTagValue(ExifTag tag) {
+        if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
+            mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+        }
+    }
+
+    private void registerIfd(int ifdType, long offset) {
+        // Cast unsigned int to int since the offset is always smaller
+        // than the size of APP1 (65536)
+        mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+    }
+
+    private void registerCompressedImage(long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+    }
+
+    private void registerUncompressedStrip(int stripIndex, long offset) {
+        mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+                , stripIndex));
+    }
+
+    private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+        short tagId = mTiffStream.readShort();
+        short dataFormat = mTiffStream.readShort();
+        long numOfComp = mTiffStream.readUnsignedInt();
+        if (numOfComp > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException(
+                    "Number of component is larger then Integer.MAX_VALUE");
+        }
+        // Some invalid image file contains invalid data type. Ignore those tags
+        if (!ExifTag.isValidType(dataFormat)) {
+            Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+            mTiffStream.skip(4);
+            return null;
+        }
+        // TODO: handle numOfComp overflow
+        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+                ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
+        int dataSize = tag.getDataSize();
+        if (dataSize > 4) {
+            long offset = mTiffStream.readUnsignedInt();
+            if (offset > Integer.MAX_VALUE) {
+                throw new ExifInvalidFormatException(
+                        "offset is larger then Integer.MAX_VALUE");
+            }
+            // Some invalid images put some undefined data before IFD0.
+            // Read the data here.
+            if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+                byte[] buf = new byte[(int) numOfComp];
+                System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+                        buf, 0, (int) numOfComp);
+                tag.setValue(buf);
+            } else {
+                tag.setOffset((int) offset);
+            }
+        } else {
+            boolean defCount = tag.hasDefinedCount();
+            // Set defined count to 0 so we can add \0 to non-terminated strings
+            tag.setHasDefinedCount(false);
+            // Read value
+            readFullTagValue(tag);
+            tag.setHasDefinedCount(defCount);
+            mTiffStream.skip(4 - dataSize);
+            // Set the offset to the position of value.
+            tag.setOffset(mTiffStream.getReadByteCount() - 4);
+        }
+        return tag;
+    }
+
+    /**
+     * Check the tag, if the tag is one of the offset tag that points to the IFD
+     * or image the caller is interested in, register the IFD or image.
+     */
+    private void checkOffsetOrImageTag(ExifTag tag) {
+        // Some invalid formattd image contains tag with 0 size.
+        if (tag.getComponentCount() == 0) {
+            return;
+        }
+        short tid = tag.getTagId();
+        int ifd = tag.getIfd();
+        if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+                    || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+                registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_INTEROPERABILITY_IFD
+                && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+            if (isThumbnailRequested()) {
+                registerCompressedImage(tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+            if (isThumbnailRequested()) {
+                mJpegSizeTag = tag;
+            }
+        } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+            if (isThumbnailRequested()) {
+                if (tag.hasValue()) {
+                    for (int i = 0; i < tag.getComponentCount(); i++) {
+                        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
+                        } else {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
+                        }
+                    }
+                } else {
+                    mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+                }
+            }
+        } else if (tid == TAG_STRIP_BYTE_COUNTS
+                && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+                &&isThumbnailRequested() && tag.hasValue()) {
+            mStripSizeTag = tag;
+        }
+    }
+
+    private boolean checkAllowed(int ifd, int tagId) {
+        int info = mInterface.getTagInfo().get(tagId);
+        if (info == ExifInterface.DEFINITION_NULL) {
+            return false;
+        }
+        return ExifInterface.isIfdAllowed(info, ifd);
+    }
+
+    protected void readFullTagValue(ExifTag tag) throws IOException {
+        // Some invalid images contains tags with wrong size, check it here
+        short type = tag.getDataType();
+        if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+                type == ExifTag.TYPE_UNSIGNED_BYTE) {
+            int size = tag.getComponentCount();
+            if (mCorrespondingEvent.size() > 0) {
+                if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+                        + size) {
+                    Object event = mCorrespondingEvent.firstEntry().getValue();
+                    if (event instanceof ImageEvent) {
+                        // Tag value overlaps thumbnail, ignore thumbnail.
+                        Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
+                        Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+                        Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+                    } else {
+                        // Tag value overlaps another tag, shorten count
+                        if (event instanceof IfdEvent) {
+                            Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        } else if (event instanceof ExifTagEvent) {
+                            Log.w(TAG, "Tag value for tag: \n"
+                                    + ((ExifTagEvent) event).tag.toString()
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        }
+                        size = mCorrespondingEvent.firstEntry().getKey()
+                                - mTiffStream.getReadByteCount();
+                        Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+                                + " setting count to: " + size);
+                        tag.forceSetComponentCount(size);
+                    }
+                }
+            }
+        }
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+            case ExifTag.TYPE_UNDEFINED: {
+                byte buf[] = new byte[tag.getComponentCount()];
+                read(buf);
+                tag.setValue(buf);
+            }
+                break;
+            case ExifTag.TYPE_ASCII:
+                tag.setValue(readString(tag.getComponentCount()));
+                break;
+            case ExifTag.TYPE_UNSIGNED_LONG: {
+                long value[] = new long[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedLong();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedShort();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_LONG: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readLong();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+        }
+        if (LOGV) {
+            Log.v(TAG, "\n" + tag.toString());
+        }
+    }
+
+    private void parseTiffHeader() throws IOException,
+            ExifInvalidFormatException {
+        short byteOrder = mTiffStream.readShort();
+        if (LITTLE_ENDIAN_TAG == byteOrder) {
+            mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+        } else if (BIG_ENDIAN_TAG == byteOrder) {
+            mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+        } else {
+            throw new ExifInvalidFormatException("Invalid TIFF header");
+        }
+
+        if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
+            throw new ExifInvalidFormatException("Invalid TIFF header");
+        }
+    }
+
+    private boolean seekTiffData(InputStream inputStream) throws IOException,
+            ExifInvalidFormatException {
+        CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
+        if (dataStream.readShort() != JpegHeader.SOI) {
+            throw new ExifInvalidFormatException("Invalid JPEG format");
+        }
+
+        short marker = dataStream.readShort();
+        while (marker != JpegHeader.EOI
+                && !JpegHeader.isSofMarker(marker)) {
+            int length = dataStream.readUnsignedShort();
+            // Some invalid formatted image contains multiple APP1,
+            // try to find the one with Exif data.
+            if (marker == JpegHeader.APP1) {
+                int header = 0;
+                short headerTail = 0;
+                if (length >= 8) {
+                    header = dataStream.readInt();
+                    headerTail = dataStream.readShort();
+                    length -= 6;
+                    if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+                        mTiffStartPosition = dataStream.getReadByteCount();
+                        mApp1End = length;
+                        mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
+                        return true;
+                    }
+                }
+            }
+            if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+                Log.w(TAG, "Invalid JPEG format.");
+                return false;
+            }
+            marker = dataStream.readShort();
+        }
+        return false;
+    }
+
+    protected int getOffsetToExifEndFromSOF() {
+        return mOffsetToApp1EndFromSOF;
+    }
+
+    protected int getTiffStartPosition() {
+        return mTiffStartPosition;
+    }
+
+    /**
+     * Reads bytes from the InputStream.
+     */
+    protected int read(byte[] buffer, int offset, int length) throws IOException {
+        return mTiffStream.read(buffer, offset, length);
+    }
+
+    /**
+     * Equivalent to read(buffer, 0, buffer.length).
+     */
+    protected int read(byte[] buffer) throws IOException {
+        return mTiffStream.read(buffer);
+    }
+
+    /**
+     * Reads a String from the InputStream with US-ASCII charset. The parser
+     * will read n bytes and convert it to ascii string. This is used for
+     * reading values of type {@link ExifTag#TYPE_ASCII}.
+     */
+    protected String readString(int n) throws IOException {
+        return readString(n, US_ASCII);
+    }
+
+    /**
+     * Reads a String from the InputStream with the given charset. The parser
+     * will read n bytes and convert it to string. This is used for reading
+     * values of type {@link ExifTag#TYPE_ASCII}.
+     */
+    protected String readString(int n, Charset charset) throws IOException {
+        if (n > 0) {
+            return mTiffStream.readString(n, charset);
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+     * InputStream.
+     */
+    protected int readUnsignedShort() throws IOException {
+        return mTiffStream.readShort() & 0xffff;
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+     * InputStream.
+     */
+    protected long readUnsignedLong() throws IOException {
+        return readLong() & 0xffffffffL;
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+     * InputStream.
+     */
+    protected Rational readUnsignedRational() throws IOException {
+        long nomi = readUnsignedLong();
+        long denomi = readUnsignedLong();
+        return new Rational(nomi, denomi);
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+     */
+    protected int readLong() throws IOException {
+        return mTiffStream.readInt();
+    }
+
+    /**
+     * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+     */
+    protected Rational readRational() throws IOException {
+        int nomi = readLong();
+        int denomi = readLong();
+        return new Rational(nomi, denomi);
+    }
+
+    private static class ImageEvent {
+        int stripIndex;
+        int type;
+
+        ImageEvent(int type) {
+            this.stripIndex = 0;
+            this.type = type;
+        }
+
+        ImageEvent(int type, int stripIndex) {
+            this.type = type;
+            this.stripIndex = stripIndex;
+        }
+    }
+
+    private static class IfdEvent {
+        int ifd;
+        boolean isRequested;
+
+        IfdEvent(int ifd, boolean isInterestedIfd) {
+            this.ifd = ifd;
+            this.isRequested = isInterestedIfd;
+        }
+    }
+
+    private static class ExifTagEvent {
+        ExifTag tag;
+        boolean isRequested;
+
+        ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+            this.tag = tag;
+            this.isRequested = isRequireByUser;
+        }
+    }
+
+    /**
+     * Gets the byte order of the current InputStream.
+     */
+    protected ByteOrder getByteOrder() {
+        return mTiffStream.getByteOrder();
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java
new file mode 100644
index 0000000..68e972f
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifReader.java
@@ -0,0 +1,92 @@
+/*
+ * 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.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
+ */
+class ExifReader {
+    private static final String TAG = "ExifReader";
+
+    private final ExifInterface mInterface;
+
+    ExifReader(ExifInterface iRef) {
+        mInterface = iRef;
+    }
+
+    /**
+     * Parses the inputStream and and returns the EXIF data in an
+     * {@link ExifData}.
+     *
+     * @throws ExifInvalidFormatException
+     * @throws IOException
+     */
+    protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+            IOException {
+        ExifParser parser = ExifParser.parse(inputStream, mInterface);
+        ExifData exifData = new ExifData(parser.getByteOrder());
+        ExifTag tag = null;
+
+        int event = parser.next();
+        while (event != ExifParser.EVENT_END) {
+            switch (event) {
+                case ExifParser.EVENT_START_OF_IFD:
+                    exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+                    break;
+                case ExifParser.EVENT_NEW_TAG:
+                    tag = parser.getTag();
+                    if (!tag.hasValue()) {
+                        parser.registerForTagValue(tag);
+                    } else {
+                        exifData.getIfdData(tag.getIfd()).setTag(tag);
+                    }
+                    break;
+                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                    tag = parser.getTag();
+                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                        parser.readFullTagValue(tag);
+                    }
+                    exifData.getIfdData(tag.getIfd()).setTag(tag);
+                    break;
+                case ExifParser.EVENT_COMPRESSED_IMAGE:
+                    byte buf[] = new byte[parser.getCompressedImageSize()];
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setCompressedThumbnail(buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the compressed thumbnail");
+                    }
+                    break;
+                case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+                    buf = new byte[parser.getStripSize()];
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setStripBytes(parser.getStripIndex(), buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the strip bytes");
+                    }
+                    break;
+            }
+            event = parser.next();
+        }
+        return exifData;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java
new file mode 100644
index 0000000..b8b3872
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifTag.java
@@ -0,0 +1,1008 @@
+/*
+ * 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.exif;
+
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
+ */
+public class ExifTag {
+    /**
+     * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+     */
+    public static final short TYPE_UNSIGNED_BYTE = 1;
+    /**
+     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+     * ASCII code. The final byte is terminated with NULL.
+     */
+    public static final short TYPE_ASCII = 2;
+    /**
+     * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+     */
+    public static final short TYPE_UNSIGNED_SHORT = 3;
+    /**
+     * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+     */
+    public static final short TYPE_UNSIGNED_LONG = 4;
+    /**
+     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+     * one is the numerator and the second one expresses the denominator.
+     */
+    public static final short TYPE_UNSIGNED_RATIONAL = 5;
+    /**
+     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+     * value depending on the field definition.
+     */
+    public static final short TYPE_UNDEFINED = 7;
+    /**
+     * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+     * (2's complement notation).
+     */
+    public static final short TYPE_LONG = 9;
+    /**
+     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+     * one is the numerator and the second one is the denominator.
+     */
+    public static final short TYPE_RATIONAL = 10;
+
+    private static Charset US_ASCII = Charset.forName("US-ASCII");
+    private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+    private static final int UNSIGNED_SHORT_MAX = 65535;
+    private static final long UNSIGNED_LONG_MAX = 4294967295L;
+    private static final long LONG_MAX = Integer.MAX_VALUE;
+    private static final long LONG_MIN = Integer.MIN_VALUE;
+
+    static {
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+        TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+        TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+        TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+        TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+    }
+
+    static final int SIZE_UNDEFINED = 0;
+
+    // Exif TagId
+    private final short mTagId;
+    // Exif Tag Type
+    private final short mDataType;
+    // If tag has defined count
+    private boolean mHasDefinedDefaultComponentCount;
+    // Actual data count in tag (should be number of elements in value array)
+    private int mComponentCountActual;
+    // The ifd that this tag should be put in
+    private int mIfd;
+    // The value (array of elements of type Tag Type)
+    private Object mValue;
+    // Value offset in exif header.
+    private int mOffset;
+
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
+    /**
+     * Returns true if the given IFD is a valid IFD.
+     */
+    public static boolean isValidIfd(int ifdId) {
+        return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+                || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+                || ifdId == IfdId.TYPE_IFD_GPS;
+    }
+
+    /**
+     * Returns true if a given type is a valid tag type.
+     */
+    public static boolean isValidType(short type) {
+        return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+                type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+                type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+                type == TYPE_LONG || type == TYPE_RATIONAL;
+    }
+
+    // Use builtTag in ExifInterface instead of constructor.
+    ExifTag(short tagId, short type, int componentCount, int ifd,
+            boolean hasDefinedComponentCount) {
+        mTagId = tagId;
+        mDataType = type;
+        mComponentCountActual = componentCount;
+        mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+        mIfd = ifd;
+        mValue = null;
+    }
+
+    /**
+     * Gets the element size of the given data type in bytes.
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_LONG
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_LONG
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
+    public static int getElementSize(short type) {
+        return TYPE_TO_SIZE_MAP[type];
+    }
+
+    /**
+     * Returns the ID of the IFD this tag belongs to.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    public int getIfd() {
+        return mIfd;
+    }
+
+    protected void setIfd(int ifdId) {
+        mIfd = ifdId;
+    }
+
+    /**
+     * Gets the TID of this tag.
+     */
+    public short getTagId() {
+        return mTagId;
+    }
+
+    /**
+     * Gets the data type of this tag
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_LONG
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_LONG
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
+    public short getDataType() {
+        return mDataType;
+    }
+
+    /**
+     * Gets the total data size in bytes of the value of this tag.
+     */
+    public int getDataSize() {
+        return getComponentCount() * getElementSize(getDataType());
+    }
+
+    /**
+     * Gets the component count of this tag.
+     */
+
+    // TODO: fix integer overflows with this
+    public int getComponentCount() {
+        return mComponentCountActual;
+    }
+
+    /**
+     * Sets the component count of this tag. Call this function before
+     * setValue() if the length of value does not match the component count.
+     */
+    protected void forceSetComponentCount(int count) {
+        mComponentCountActual = count;
+    }
+
+    /**
+     * Returns true if this ExifTag contains value; otherwise, this tag will
+     * contain an offset value that is determined when the tag is written.
+     */
+    public boolean hasValue() {
+        return mValue != null;
+    }
+
+    /**
+     * Sets integer values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(int[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+                mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+            return false;
+        } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
+        }
+        mValue = data;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets integer value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition of this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(int value) {
+        return setValue(new int[] {
+                value
+        });
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(long[] value) {
+        if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(long value) {
+        return setValue(new long[] {
+                value
+        });
+    }
+
+    /**
+     * Sets a string value into this tag. This method should be used for tags of
+     * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+     * Characters that cannot be converted are replaced with '?'. The length of
+     * the string must be equal to either (component count -1) or (component
+     * count). The final byte will be set to the string null terminator '\0',
+     * overwriting the last character in the string if the value.length is equal
+     * to the component count. This method will fail if:
+     * <ul>
+     * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+     * <li>The length of the string is not equal to (component count -1) or
+     * (component count) in the definition for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(String value) {
+        if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+
+        byte[] buf = value.getBytes(US_ASCII);
+        byte[] finalBuf = buf;
+        if (buf.length > 0) {
+            finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+                .copyOf(buf, buf.length + 1);
+        } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+            finalBuf = new byte[] { 0 };
+        }
+        int count = finalBuf.length;
+        if (checkBadComponentCount(count)) {
+            return false;
+        }
+        mComponentCountActual = count;
+        mValue = finalBuf;
+        return true;
+    }
+
+    /**
+     * Sets Rational values into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+            return false;
+        } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+            return false;
+        }
+
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets a Rational value into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational value) {
+        return setValue(new Rational[] {
+                value
+        });
+    }
+
+    /**
+     * Sets byte values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The length does NOT match the component count in the definition for
+     * this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(byte[] value, int offset, int length) {
+        if (checkBadComponentCount(length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+        mValue = new byte[length];
+        System.arraycopy(value, offset, mValue, 0, length);
+        mComponentCountActual = length;
+        return true;
+    }
+
+    /**
+     * Equivalent to setValue(value, 0, value.length).
+     */
+    public boolean setValue(byte[] value) {
+        return setValue(value, 0, value.length);
+    }
+
+    /**
+     * Sets byte value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(byte value) {
+        return setValue(new byte[] {
+                value
+        });
+    }
+
+    /**
+     * Sets the value for this tag using an appropriate setValue method for the
+     * given object. This method will fail if:
+     * <ul>
+     * <li>The corresponding setValue method for the class of the object passed
+     * in would fail.</li>
+     * <li>There is no obvious way to cast the object passed in into an EXIF tag
+     * type.</li>
+     * </ul>
+     */
+    public boolean setValue(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (obj instanceof Short) {
+            return setValue(((Short) obj).shortValue() & 0x0ffff);
+        } else if (obj instanceof String) {
+            return setValue((String) obj);
+        } else if (obj instanceof int[]) {
+            return setValue((int[]) obj);
+        } else if (obj instanceof long[]) {
+            return setValue((long[]) obj);
+        } else if (obj instanceof Rational) {
+            return setValue((Rational) obj);
+        } else if (obj instanceof Rational[]) {
+            return setValue((Rational[]) obj);
+        } else if (obj instanceof byte[]) {
+            return setValue((byte[]) obj);
+        } else if (obj instanceof Integer) {
+            return setValue(((Integer) obj).intValue());
+        } else if (obj instanceof Long) {
+            return setValue(((Long) obj).longValue());
+        } else if (obj instanceof Byte) {
+            return setValue(((Byte) obj).byteValue());
+        } else if (obj instanceof Short[]) {
+            // Nulls in this array are treated as zeroes.
+            Short[] arr = (Short[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+            }
+            return setValue(fin);
+        } else if (obj instanceof Integer[]) {
+            // Nulls in this array are treated as zeroes.
+            Integer[] arr = (Integer[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Long[]) {
+            // Nulls in this array are treated as zeroes.
+            Long[] arr = (Long[]) obj;
+            long[] fin = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Byte[]) {
+            // Nulls in this array are treated as zeroes.
+            Byte[] arr = (Byte[]) obj;
+            byte[] fin = new byte[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+            }
+            return setValue(fin);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Sets a timestamp to this tag. The method converts the timestamp with the
+     * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+     * method will fail if the data type is not {@link #TYPE_ASCII} or the
+     * component count of this tag is not 20 or undefined.
+     *
+     * @param time the number of milliseconds since Jan. 1, 1970 GMT
+     * @return true on success
+     */
+    public boolean setTimeValue(long time) {
+        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+        synchronized (TIME_FORMAT) {
+            return setValue(TIME_FORMAT.format(new Date(time)));
+        }
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @return the value as a String, or null if the tag's value does not exist
+     *         or cannot be converted to a String.
+     */
+    public String getValueAsString() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof String) {
+            return (String) mValue;
+        } else if (mValue instanceof byte[]) {
+            return new String((byte[]) mValue, US_ASCII);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @param defaultValue the String to return if the tag's value does not
+     *            exist or cannot be converted to a String.
+     * @return the tag's value as a String, or the defaultValue.
+     */
+    public String getValueAsString(String defaultValue) {
+        String s = getValueAsString();
+        if (s == null) {
+            return defaultValue;
+        }
+        return s;
+    }
+
+    /**
+     * Gets the value as a byte array. This method should be used for tags of
+     * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @return the value as a byte array, or null if the tag's value does not
+     *         exist or cannot be converted to a byte array.
+     */
+    public byte[] getValueAsBytes() {
+        if (mValue instanceof byte[]) {
+            return (byte[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a byte. If there are more than 1 bytes in this value,
+     * gets the first byte. This method should be used for tags of type
+     * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @param defaultValue the byte to return if tag's value does not exist or
+     *            cannot be converted to a byte.
+     * @return the tag's value as a byte, or the defaultValue.
+     */
+    public byte getValueAsByte(byte defaultValue) {
+        byte[] b = getValueAsBytes();
+        if (b == null || b.length < 1) {
+            return defaultValue;
+        }
+        return b[0];
+    }
+
+    /**
+     * Gets the value as an array of Rationals. This method should be used for
+     * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @return the value as as an array of Rationals, or null if the tag's value
+     *         does not exist or cannot be converted to an array of Rationals.
+     */
+    public Rational[] getValueAsRationals() {
+        if (mValue instanceof Rational[]) {
+            return (Rational[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the Rational to return if tag's value does not exist
+     *            or cannot be converted to a Rational.
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(Rational defaultValue) {
+        Rational[] r = getValueAsRationals();
+        if (r == null || r.length < 1) {
+            return defaultValue;
+        }
+        return r[0];
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the numerator of the Rational to return if tag's
+     *            value does not exist or cannot be converted to a Rational (the
+     *            denominator will be 1).
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(long defaultValue) {
+        Rational defaultVal = new Rational(defaultValue, 1);
+        return getValueAsRational(defaultVal);
+    }
+
+    /**
+     * Gets the value as an array of ints. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of ints, or null if the tag's value does
+     *         not exist or cannot be converted to an array of ints.
+     */
+    public int[] getValueAsInts() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof long[]) {
+            long[] val = (long[]) mValue;
+            int[] arr = new int[val.length];
+            for (int i = 0; i < val.length; i++) {
+                arr[i] = (int) val[i]; // Truncates
+            }
+            return arr;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as an int. If there are more than 1 ints in this value,
+     * gets the first one. This method should be used for tags of type
+     * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the int to return if tag's value does not exist or
+     *            cannot be converted to an int.
+     * @return the tag's value as a int, or the defaultValue.
+     */
+    public int getValueAsInt(int defaultValue) {
+        int[] i = getValueAsInts();
+        if (i == null || i.length < 1) {
+            return defaultValue;
+        }
+        return i[0];
+    }
+
+    /**
+     * Gets the value as an array of longs. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of longs, or null if the tag's value
+     *         does not exist or cannot be converted to an array of longs.
+     */
+    public long[] getValueAsLongs() {
+        if (mValue instanceof long[]) {
+            return (long[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value or null if none exists. If there are more than 1 longs in
+     * this value, gets the first one. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the long to return if tag's value does not exist or
+     *            cannot be converted to a long.
+     * @return the tag's value as a long, or the defaultValue.
+     */
+    public long getValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l == null || l.length < 1) {
+            return defaultValue;
+        }
+        return l[0];
+    }
+
+    /**
+     * Gets the tag's value or null if none exists.
+     */
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Gets a long representation of the value.
+     *
+     * @param defaultValue value to return if there is no value or value is a
+     *            rational with a denominator of 0.
+     * @return the tag's value as a long, or defaultValue if no representation
+     *         exists.
+     */
+    public long forceGetValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l != null && l.length >= 1) {
+            return l[0];
+        }
+        byte[] b = getValueAsBytes();
+        if (b != null && b.length >= 1) {
+            return b[0];
+        }
+        Rational[] r = getValueAsRationals();
+        if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+            return (long) r[0].toDouble();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets a string representation of the value.
+     */
+    public String forceGetValueAsString() {
+        if (mValue == null) {
+            return "";
+        } else if (mValue instanceof byte[]) {
+            if (mDataType == TYPE_ASCII) {
+                return new String((byte[]) mValue, US_ASCII);
+            } else {
+                return Arrays.toString((byte[]) mValue);
+            }
+        } else if (mValue instanceof long[]) {
+            if (((long[]) mValue).length == 1) {
+                return String.valueOf(((long[]) mValue)[0]);
+            } else {
+                return Arrays.toString((long[]) mValue);
+            }
+        } else if (mValue instanceof Object[]) {
+            if (((Object[]) mValue).length == 1) {
+                Object val = ((Object[]) mValue)[0];
+                if (val == null) {
+                    return "";
+                } else {
+                    return val.toString();
+                }
+            } else {
+                return Arrays.toString((Object[]) mValue);
+            }
+        } else {
+            return mValue.toString();
+        }
+    }
+
+    /**
+     * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+     * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+     * {@link #getRational(int)} instead.
+     *
+     * @exception IllegalArgumentException if the data type is
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected long getValueAt(int index) {
+        if (mValue instanceof long[]) {
+            return ((long[]) mValue)[index];
+        } else if (mValue instanceof byte[]) {
+            return ((byte[]) mValue)[index];
+        }
+        throw new IllegalArgumentException("Cannot get integer value from "
+                + convertTypeToString(mDataType));
+    }
+
+    /**
+     * Gets the {@link #TYPE_ASCII} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_ASCII}.
+     */
+    protected String getString() {
+        if (mDataType != TYPE_ASCII) {
+            throw new IllegalArgumentException("Cannot get ASCII value from "
+                    + convertTypeToString(mDataType));
+        }
+        return new String((byte[]) mValue, US_ASCII);
+    }
+
+    /*
+     * Get the converted ascii byte. Used by ExifOutputStream.
+     */
+    protected byte[] getStringByte() {
+        return (byte[]) mValue;
+    }
+
+    /**
+     * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected Rational getRational(int index) {
+        if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+            throw new IllegalArgumentException("Cannot get RATIONAL value from "
+                    + convertTypeToString(mDataType));
+        }
+        return ((Rational[]) mValue)[index];
+    }
+
+    /**
+     * Equivalent to getBytes(buffer, 0, buffer.length).
+     */
+    protected void getBytes(byte[] buf) {
+        getBytes(buf, 0, buf.length);
+    }
+
+    /**
+     * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+     *
+     * @param buf the byte array in which to store the bytes read.
+     * @param offset the initial position in buffer to store the bytes.
+     * @param length the maximum number of bytes to store in buffer. If length >
+     *            component count, only the valid bytes will be stored.
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     */
+    protected void getBytes(byte[] buf, int offset, int length) {
+        if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+            throw new IllegalArgumentException("Cannot get BYTE value from "
+                    + convertTypeToString(mDataType));
+        }
+        System.arraycopy(mValue, 0, buf, offset,
+                (length > mComponentCountActual) ? mComponentCountActual : length);
+    }
+
+    /**
+     * Gets the offset of this tag. This is only valid if this data size > 4 and
+     * contains an offset to the location of the actual value.
+     */
+    protected int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Sets the offset of this tag.
+     */
+    protected void setOffset(int offset) {
+        mOffset = offset;
+    }
+
+    protected void setHasDefinedCount(boolean d) {
+        mHasDefinedDefaultComponentCount = d;
+    }
+
+    protected boolean hasDefinedCount() {
+        return mHasDefinedDefaultComponentCount;
+    }
+
+    private boolean checkBadComponentCount(int count) {
+        if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+            return true;
+        }
+        return false;
+    }
+
+    private static String convertTypeToString(short type) {
+        switch (type) {
+            case TYPE_UNSIGNED_BYTE:
+                return "UNSIGNED_BYTE";
+            case TYPE_ASCII:
+                return "ASCII";
+            case TYPE_UNSIGNED_SHORT:
+                return "UNSIGNED_SHORT";
+            case TYPE_UNSIGNED_LONG:
+                return "UNSIGNED_LONG";
+            case TYPE_UNSIGNED_RATIONAL:
+                return "UNSIGNED_RATIONAL";
+            case TYPE_UNDEFINED:
+                return "UNDEFINED";
+            case TYPE_LONG:
+                return "LONG";
+            case TYPE_RATIONAL:
+                return "RATIONAL";
+            default:
+                return "";
+        }
+    }
+
+    private boolean checkOverflowForUnsignedShort(int[] value) {
+        for (int v : value) {
+            if (v > UNSIGNED_SHORT_MAX || v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(long[] value) {
+        for (long v : value) {
+            if (v < 0 || v > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(int[] value) {
+        for (int v : value) {
+            if (v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < 0 || v.getDenominator() < 0
+                    || v.getNumerator() > UNSIGNED_LONG_MAX
+                    || v.getDenominator() > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+                    || v.getNumerator() > LONG_MAX
+                    || v.getDenominator() > LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof ExifTag) {
+            ExifTag tag = (ExifTag) obj;
+            if (tag.mTagId != this.mTagId
+                    || tag.mComponentCountActual != this.mComponentCountActual
+                    || tag.mDataType != this.mDataType) {
+                return false;
+            }
+            if (mValue != null) {
+                if (tag.mValue == null) {
+                    return false;
+                } else if (mValue instanceof long[]) {
+                    if (!(tag.mValue instanceof long[])) {
+                        return false;
+                    }
+                    return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+                } else if (mValue instanceof Rational[]) {
+                    if (!(tag.mValue instanceof Rational[])) {
+                        return false;
+                    }
+                    return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+                } else if (mValue instanceof byte[]) {
+                    if (!(tag.mValue instanceof byte[])) {
+                        return false;
+                    }
+                    return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
+                } else {
+                    return mValue.equals(tag.mValue);
+                }
+            } else {
+                return tag.mValue == null;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+                + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+                + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+    }
+
+}
diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java
new file mode 100644
index 0000000..093944a
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdData.java
@@ -0,0 +1,152 @@
+/*
+ * 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.exif;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+    private final int mIfdId;
+    private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
+    private int mOffsetToNextIfd = 0;
+    private static final int[] sIfds = {
+            IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+            IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+    };
+    /**
+     * Creates an IfdData with given IFD ID.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    IfdData(int ifdId) {
+        mIfdId = ifdId;
+    }
+
+    static protected int[] getIfds() {
+        return sIfds;
+    }
+
+    /**
+     * Get a array the contains all {@link ExifTag} in this IFD.
+     */
+    protected ExifTag[] getAllTags() {
+        return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
+    }
+
+    /**
+     * Gets the ID of this IFD.
+     *
+     * @see IfdId#TYPE_IFD_0
+     * @see IfdId#TYPE_IFD_1
+     * @see IfdId#TYPE_IFD_EXIF
+     * @see IfdId#TYPE_IFD_GPS
+     * @see IfdId#TYPE_IFD_INTEROPERABILITY
+     */
+    protected int getId() {
+        return mIfdId;
+    }
+
+    /**
+     * Gets the {@link ExifTag} with given tag id. Return null if there is no
+     * such tag.
+     */
+    protected ExifTag getTag(short tagId) {
+        return mExifTags.get(tagId);
+    }
+
+    /**
+     * Adds or replaces a {@link ExifTag}.
+     */
+    protected ExifTag setTag(ExifTag tag) {
+        tag.setIfd(mIfdId);
+        return mExifTags.put(tag.getTagId(), tag);
+    }
+
+    protected boolean checkCollision(short tagId) {
+        return mExifTags.get(tagId) != null;
+    }
+
+    /**
+     * Removes the tag of the given ID
+     */
+    protected void removeTag(short tagId) {
+        mExifTags.remove(tagId);
+    }
+
+    /**
+     * Gets the tags count in the IFD.
+     */
+    protected int getTagCount() {
+        return mExifTags.size();
+    }
+
+    /**
+     * Sets the offset of next IFD.
+     */
+    protected void setOffsetToNextIfd(int offset) {
+        mOffsetToNextIfd = offset;
+    }
+
+    /**
+     * Gets the offset of next IFD.
+     */
+    protected int getOffsetToNextIfd() {
+        return mOffsetToNextIfd;
+    }
+
+    /**
+     * Returns true if all tags in this two IFDs are equal. Note that tags of
+     * IFDs offset or thumbnail offset will be ignored.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof IfdData) {
+            IfdData data = (IfdData) obj;
+            if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+                ExifTag[] tags = data.getAllTags();
+                for (ExifTag tag : tags) {
+                    if (ExifInterface.isOffsetTag(tag.getTagId())) {
+                        continue;
+                    }
+                    ExifTag tag2 = mExifTags.get(tag.getTagId());
+                    if (!tag.equals(tag2)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/IfdId.java b/src/com/android/gallery3d/exif/IfdId.java
new file mode 100644
index 0000000..7842edb
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdId.java
@@ -0,0 +1,31 @@
+/*
+ * 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.exif;
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+public interface IfdId {
+    public static final int TYPE_IFD_0 = 0;
+    public static final int TYPE_IFD_1 = 1;
+    public static final int TYPE_IFD_EXIF = 2;
+    public static final int TYPE_IFD_INTEROPERABILITY = 3;
+    public static final int TYPE_IFD_GPS = 4;
+    /* This is used in ExifData to allocate enough IfdData */
+    static final int TYPE_IFD_COUNT = 5;
+
+}
diff --git a/src/com/android/gallery3d/exif/JpegHeader.java b/src/com/android/gallery3d/exif/JpegHeader.java
new file mode 100644
index 0000000..e3e787e
--- /dev/null
+++ b/src/com/android/gallery3d/exif/JpegHeader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.exif;
+
+class JpegHeader {
+    public static final short SOI =  (short) 0xFFD8;
+    public static final short APP1 = (short) 0xFFE1;
+    public static final short APP0 = (short) 0xFFE0;
+    public static final short EOI = (short) 0xFFD9;
+
+    /**
+     *  SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
+     *  and DAC marker.
+     */
+    public static final short SOF0 = (short) 0xFFC0;
+    public static final short SOF15 = (short) 0xFFCF;
+    public static final short DHT = (short) 0xFFC4;
+    public static final short JPG = (short) 0xFFC8;
+    public static final short DAC = (short) 0xFFCC;
+
+    public static final boolean isSofMarker(short marker) {
+        return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
+                && marker != DAC;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
new file mode 100644
index 0000000..428e6b9
--- /dev/null
+++ b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * 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.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+    private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+    public OrderedDataOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public OrderedDataOutputStream setByteOrder(ByteOrder order) {
+        mByteBuffer.order(order);
+        return this;
+    }
+
+    public OrderedDataOutputStream writeShort(short value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putShort(value);
+        out.write(mByteBuffer.array(), 0, 2);
+        return this;
+    }
+
+    public OrderedDataOutputStream writeInt(int value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putInt(value);
+        out.write(mByteBuffer.array());
+        return this;
+    }
+
+    public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+        writeInt((int) rational.getNumerator());
+        writeInt((int) rational.getDenominator());
+        return this;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java
new file mode 100644
index 0000000..591d63f
--- /dev/null
+++ b/src/com/android/gallery3d/exif/Rational.java
@@ -0,0 +1,88 @@
+/*
+ * 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.exif;
+
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
+public class Rational {
+
+    private final long mNumerator;
+    private final long mDenominator;
+
+    /**
+     * Create a Rational with a given numerator and denominator.
+     *
+     * @param nominator
+     * @param denominator
+     */
+    public Rational(long nominator, long denominator) {
+        mNumerator = nominator;
+        mDenominator = denominator;
+    }
+
+    /**
+     * Create a copy of a Rational.
+     */
+    public Rational(Rational r) {
+        mNumerator = r.mNumerator;
+        mDenominator = r.mDenominator;
+    }
+
+    /**
+     * Gets the numerator of the rational.
+     */
+    public long getNumerator() {
+        return mNumerator;
+    }
+
+    /**
+     * Gets the denominator of the rational
+     */
+    public long getDenominator() {
+        return mDenominator;
+    }
+
+    /**
+     * Gets the rational value as type double. Will cause a divide-by-zero error
+     * if the denominator is 0.
+     */
+    public double toDouble() {
+        return mNumerator / (double) mDenominator;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof Rational) {
+            Rational data = (Rational) obj;
+            return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return mNumerator + "/" + mDenominator;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
new file mode 100644
index 0000000..2e77b90
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -0,0 +1,212 @@
+/*
+ * 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 {
+
+    @SuppressWarnings("unused")
+    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;
+
+    private boolean mHasBorder;
+
+    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 boolean isFlippedVertically() {
+      return false;
+    }
+
+    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;
+    }
+
+    // Returns true if the texture has one pixel transparent border around the
+    // actual content. This is used to avoid jigged edges.
+    //
+    // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
+    // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+    // covered by the texture will use the color of the edge texel. If we add
+    // the transparent border, the color of the edge texel will be mixed with
+    // appropriate amount of transparent.
+    //
+    // Currently our background is black, so we can draw the thumbnails without
+    // enabling blending.
+    public boolean hasBorder() {
+        return mHasBorder;
+    }
+
+    protected void setBorder(boolean hasBorder) {
+        mHasBorder = hasBorder;
+    }
+
+    @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);
+
+    // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+    abstract protected int getTarget();
+
+    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);
+    }
+
+    // This is for deciding if we can call Bitmap's recycle().
+    // We cannot call Bitmap's recycle() in finalizer because at that point
+    // the finalizer of Bitmap may already be called so recycle() will crash.
+    public static boolean inFinalizer() {
+        return sInFinalizer.get() != 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..100b0b3
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -0,0 +1,54 @@
+/*
+ * 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 junit.framework.Assert;
+
+// 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) {
+        this(bitmap, false);
+    }
+
+    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
+        super(hasBorder);
+        Assert.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..305e905
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -0,0 +1,217 @@
+/*
+ * 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.Rect;
+import android.graphics.RectF;
+
+import javax.microedition.khronos.opengles.GL11;
+
+//
+// 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 clearBuffer(float[] argb);
+
+    // Sets and gets the current alpha, alpha must be in [0, 1].
+    public abstract void setAlpha(float alpha);
+
+    public abstract float getAlpha();
+
+    // (current alpha) = (current alpha) * alpha
+    public abstract void multiplyAlpha(float alpha);
+
+    // Change the current transform matrix.
+    public abstract void translate(float x, float y, float z);
+
+    public abstract void translate(float x, float y);
+
+    public abstract void scale(float sx, float sy, float sz);
+
+    public abstract void rotate(float angle, float x, float y, float z);
+
+    public abstract void multiplyMatrix(float[] mMatrix, int offset);
+
+    // Pushes the configuration state (matrix, and alpha) onto
+    // a private stack.
+    public abstract void save();
+
+    // 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_ALPHA = 0x01;
+    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 line using the specified paint from (x1, y1) to (x2, y2).
+    // (Both end points are included).
+    public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
+
+    // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
+    // (Both end points are included).
+    public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
+
+    // Fills the specified rectangle with the specified color.
+    public abstract void fillRect(float x, float y, float width, float height, int color);
+
+    // Draws a texture to the specified rectangle.
+    public abstract void drawTexture(
+            BasicTexture texture, int x, int y, int width, int height);
+
+    public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+            int uvBuffer, int indexBuffer, int indexCount);
+
+    // Draws the source rectangle part of the texture to the target rectangle.
+    public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
+
+    // Draw a texture with a specified texture transform.
+    public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
+                int x, int y, int w, int h);
+
+    // Draw two textures to the specified rectangle. The actual texture used is
+    // from * (1 - ratio) + to * ratio
+    // The two textures must have the same size.
+    public abstract void drawMixed(BasicTexture from, int toColor,
+            float ratio, int x, int y, int w, int h);
+
+    // Draw a region of a texture and a specified color to the specified
+    // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
+    // The region of the texture is defined by parameter "src". The target
+    // rectangle is specified by parameter "target".
+    public abstract void drawMixed(BasicTexture from, int toColor,
+            float ratio, RectF src, 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 specified buffer object, similar to unloadTexture.
+    public abstract void deleteBuffer(int bufferId);
+
+    // Delete the textures and buffers in GL side. This function should only be
+    // called in the GL thread.
+    public abstract void deleteRecycledResources();
+
+    // Dump statistics information and clear the counters. For debug only.
+    public abstract void dumpStatisticsAndClear();
+
+    public abstract void beginRenderTarget(RawTexture texture);
+
+    public abstract void endRenderTarget();
+
+    /**
+     * 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);
+
+    /**
+     * Generates buffers and uploads the element array buffer data.
+     *
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
+     */
+    public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
+
+    /**
+     * After LightCycle makes GL calls, this method is called to restore the GL
+     * configuration to the one expected by GLCanvas.
+     */
+    public abstract void recoverFromLightCycle();
+
+    /**
+     * Gets the bounds given by x, y, width, and height as well as the internal
+     * matrix state. There is no special handling for non-90-degree rotations.
+     * It only considers the lower-left and upper-right corners as the bounds.
+     *
+     * @param bounds The output bounds to write to.
+     * @param x The left side of the input rectangle.
+     * @param y The bottom of the input rectangle.
+     * @param width The width of the input rectangle.
+     * @param height The height of the input rectangle.
+     */
+    public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
new file mode 100644
index 0000000..4ead131
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -0,0 +1,1009 @@
+/*
+ * 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.Rect;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.util.IntArray;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+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 float OPAQUE_ALPHA = 0.95f;
+
+    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 COUNT_LINE_VERTEX = 2;
+    private static final int COUNT_RECT_VERTEX = 4;
+    private static final int OFFSET_FILL_RECT = 0;
+    private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX;
+    private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX;
+
+    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 float[] BOUNDS_COORDINATES = {
+        0, 0, 0, 1,
+        1, 1, 0, 1,
+    };
+
+    private static final String POSITION_ATTRIBUTE = "aPosition";
+    private static final String COLOR_UNIFORM = "uColor";
+    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_COORD_ATTRIBUTE = "aTextureCoordinate";
+
+    private static final String DRAW_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "}\n";
+
+    private static final String DRAW_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = " + COLOR_UNIFORM + ";\n"
+            + "}\n";
+
+    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 MESH_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "attribute vec2 " + TEXTURE_COORD_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_COORD_ATTRIBUTE + ";\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 String OES_TEXTURE_FRAGMENT_SHADER = ""
+            + "#extension GL_OES_EGL_image_external : require\n"
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform samplerExternalOES " + 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 float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
+    private IntArray mSaveFlags = new IntArray();
+
+    private int mCurrentAlphaIndex = 0;
+    private int mCurrentMatrixIndex = 0;
+
+    // Viewport size
+    private int mWidth;
+    private int mHeight;
+
+    // Projection matrix
+    private float[] mProjectionMatrix = new float[MATRIX_SIZE];
+
+    // Screen size for when we aren't bound to a texture
+    private int mScreenWidth;
+    private int mScreenHeight;
+
+    // GL programs
+    private int mDrawProgram;
+    private int mTextureProgram;
+    private int mOesTextureProgram;
+    private int mMeshProgram;
+
+    // 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 -- draw
+    private static final int INDEX_COLOR = 2;
+
+    // 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;
+
+    // Handle indices -- mesh
+    private static final int INDEX_TEXTURE_COORD = 2;
+
+    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();
+        }
+    }
+
+    ShaderParameter[] mDrawParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
+    };
+    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
+    };
+    ShaderParameter[] mOesTextureParameters = {
+            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
+    };
+    ShaderParameter[] mMeshParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+
+    private final IntArray mUnboundTextures = new IntArray();
+    private final IntArray mDeleteBuffers = new IntArray();
+
+    // Keep track of statistics for debugging
+    private int mCountDrawMesh = 0;
+    private int mCountTextureRect = 0;
+    private int mCountFillRect = 0;
+    private int mCountDrawLine = 0;
+
+    // Buffer for framebuffer IDs -- we keep track so we can switch the attached
+    // texture.
+    private int[] mFrameBuffer = new int[1];
+
+    // Bound textures.
+    private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
+
+    // Temporary variables used within calculations
+    private final float[] mTempMatrix = new float[32];
+    private final float[] mTempColor = new float[4];
+    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);
+        mAlphas[mCurrentAlphaIndex] = 1f;
+        mTargetTextures.add(null);
+
+        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
+        mBoxCoordinates = uploadBuffer(boxBuffer);
+
+        int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);
+        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
+        int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);
+        int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);
+        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
+        int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+                OES_TEXTURE_FRAGMENT_SHADER);
+
+        mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);
+        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
+                mTextureParameters);
+        mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,
+                mOesTextureParameters);
+        mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);
+        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);
+        if (getTargetTexture() == null) {
+            mScreenWidth = width;
+            mScreenHeight = height;
+            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();
+    }
+
+    @Override
+    public void clearBuffer(float[] argb) {
+        GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    @Override
+    public float getAlpha() {
+        return mAlphas[mCurrentAlphaIndex];
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        mAlphas[mCurrentAlphaIndex] = alpha;
+    }
+
+    @Override
+    public void multiplyAlpha(float alpha) {
+        setAlpha(getAlpha() * alpha);
+    }
+
+    @Override
+    public void translate(float x, float y, float z) {
+        Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z);
+    }
+
+    // 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 scale(float sx, float sy, float sz) {
+        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz);
+    }
+
+    @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 multiplyMatrix(float[] matrix, int offset) {
+        float[] temp = mTempMatrix;
+        float[] currentMatrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset);
+        System.arraycopy(temp, 0, currentMatrix, index, 16);
+    }
+
+    @Override
+    public void save() {
+        save(SAVE_FLAG_ALL);
+    }
+
+    @Override
+    public void save(int saveFlags) {
+        boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (saveAlpha) {
+            float currentAlpha = getAlpha();
+            mCurrentAlphaIndex++;
+            if (mAlphas.length <= mCurrentAlphaIndex) {
+                mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2);
+            }
+            mAlphas[mCurrentAlphaIndex] = currentAlpha;
+        }
+        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 restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (restoreAlpha) {
+            mCurrentAlphaIndex--;
+        }
+        boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (restoreMatrix) {
+            mCurrentMatrixIndex -= MATRIX_SIZE;
+        }
+    }
+
+    @Override
+    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
+        draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
+                paint);
+        mCountDrawLine++;
+    }
+
+    @Override
+    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
+        draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint);
+        mCountDrawLine++;
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            GLPaint paint) {
+        draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth());
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            int color, float lineWidth) {
+        prepareDraw(offset, color, lineWidth);
+        draw(mDrawParameters, type, count, x, y, width, height);
+    }
+
+    private void prepareDraw(int offset, int color, float lineWidth) {
+        GLES20.glUseProgram(mDrawProgram);
+        checkError();
+        if (lineWidth > 0) {
+            GLES20.glLineWidth(lineWidth);
+            checkError();
+        }
+        float[] colorArray = getColor(color);
+        boolean blendingEnabled = (colorArray[3] < 1f);
+        enableBlending(blendingEnabled);
+        if (blendingEnabled) {
+            GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
+            checkError();
+        }
+
+        GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);
+        setPosition(mDrawParameters, offset);
+        checkError();
+    }
+
+    private float[] getColor(int color) {
+        float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha();
+        float red = ((color >>> 16) & 0xFF) / 255f * alpha;
+        float green = ((color >>> 8) & 0xFF) / 255f * alpha;
+        float blue = (color & 0xFF) / 255f * alpha;
+        mTempColor[0] = red;
+        mTempColor[1] = green;
+        mTempColor[2] = blue;
+        mTempColor[3] = alpha;
+        return mTempColor;
+    }
+
+    private void enableBlending(boolean enableBlending) {
+        if (enableBlending) {
+            GLES20.glEnable(GLES20.GL_BLEND);
+            checkError();
+        } else {
+            GLES20.glDisable(GLES20.GL_BLEND);
+            checkError();
+        }
+    }
+
+    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 fillRect(float x, float y, float width, float height, int color) {
+        draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
+                color, 0f);
+        mCountFillRect++;
+    }
+
+    @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) {
+        int left = 0;
+        int top = 0;
+        int right = texture.getWidth();
+        int bottom = texture.getHeight();
+        if (texture.hasBorder()) {
+            left = 1;
+            top = 1;
+            right -= 1;
+            bottom -= 1;
+        }
+        outRect.set(left, top, right, bottom);
+    }
+
+    @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);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w,
+            int h) {
+        if (w <= 0 || h <= 0) {
+            return;
+        }
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawTextureRect(texture, textureTransform, 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();
+        if (texture.isFlippedVertically()) {
+            save(SAVE_FLAG_MATRIX);
+            translate(0, target.centerY());
+            scale(1, -1, 1);
+            translate(0, -target.centerY());
+        }
+        draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+                target.width(), target.height());
+        if (texture.isFlippedVertically()) {
+            restore();
+        }
+        mCountTextureRect++;
+    }
+
+    private ShaderParameter[] prepareTexture(BasicTexture texture) {
+        ShaderParameter[] params;
+        int program;
+        if (texture.getTarget() == GLES20.GL_TEXTURE_2D) {
+            params = mTextureParameters;
+            program = mTextureProgram;
+        } else {
+            params = mOesTextureParameters;
+            program = mOesTextureProgram;
+        }
+        prepareTexture(texture, program, params);
+        return params;
+    }
+
+    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+        GLES20.glUseProgram(program);
+        checkError();
+        enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        checkError();
+        texture.onBind(this);
+        GLES20.glBindTexture(texture.getTarget(), texture.getId());
+        checkError();
+        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
+        checkError();
+        GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
+        checkError();
+    }
+
+    @Override
+    public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
+            int indexBuffer, int indexCount) {
+        prepareTexture(texture, mMeshProgram, mMeshParameters);
+
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
+        checkError();
+        int positionHandle = mMeshParameters[INDEX_POSITION].handle;
+        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+                VERTEX_STRIDE, 0);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
+        checkError();
+        int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
+        GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+                false, VERTEX_STRIDE, 0);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+        checkError();
+
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glEnableVertexAttribArray(texCoordHandle);
+        checkError();
+
+        setMatrix(mMeshParameters, x, y, 1, 1);
+        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
+        checkError();
+
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDisableVertexAttribArray(texCoordHandle);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+        checkError();
+        mCountDrawMesh++;
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) {
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect);
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        save(SAVE_FLAG_ALPHA);
+
+        float currentAlpha = getAlpha();
+        float cappedRatio = Math.min(1f, Math.max(0f, ratio));
+
+        float textureAlpha = (1f - cappedRatio) * currentAlpha;
+        setAlpha(textureAlpha);
+        drawTexture(texture, source, target);
+
+        float colorAlpha = cappedRatio * currentAlpha;
+        setAlpha(colorAlpha);
+        fillRect(target.left, target.top, target.width(), target.height(), toColor);
+
+        restore();
+    }
+
+    @Override
+    public boolean unloadTexture(BasicTexture texture) {
+        boolean unload = texture.isLoaded();
+        if (unload) {
+            synchronized (mUnboundTextures) {
+                mUnboundTextures.add(texture.getId());
+            }
+        }
+        return unload;
+    }
+
+    @Override
+    public void deleteBuffer(int bufferId) {
+        synchronized (mUnboundTextures) {
+            mDeleteBuffers.add(bufferId);
+        }
+    }
+
+    @Override
+    public void deleteRecycledResources() {
+        synchronized (mUnboundTextures) {
+            IntArray ids = mUnboundTextures;
+            if (mUnboundTextures.size() > 0) {
+                mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+
+            ids = mDeleteBuffers;
+            if (ids.size() > 0) {
+                mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+        }
+    }
+
+    @Override
+    public void dumpStatisticsAndClear() {
+        String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
+                mCountTextureRect, mCountFillRect, mCountDrawLine);
+        mCountDrawMesh = 0;
+        mCountTextureRect = 0;
+        mCountFillRect = 0;
+        mCountDrawLine = 0;
+        Log.d(TAG, line);
+    }
+
+    @Override
+    public void endRenderTarget() {
+        RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1);
+        RawTexture texture = getTargetTexture();
+        setRenderTarget(oldTexture, texture);
+        restore(); // restore matrix and alpha
+    }
+
+    @Override
+    public void beginRenderTarget(RawTexture texture) {
+        save(); // save matrix and alpha and blending
+        RawTexture oldTexture = getTargetTexture();
+        mTargetTextures.add(texture);
+        setRenderTarget(oldTexture, texture);
+    }
+
+    private RawTexture getTargetTexture() {
+        return mTargetTextures.get(mTargetTextures.size() - 1);
+    }
+
+    private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) {
+        if (oldTexture == null && texture != null) {
+            GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
+            checkError();
+        } else if (oldTexture != null && texture == null) {
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+            checkError();
+            GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+        }
+
+        if (texture == null) {
+            setSize(mScreenWidth, mScreenHeight);
+        } else {
+            setSize(texture.getWidth(), texture.getHeight());
+
+            if (!texture.isLoaded()) {
+                texture.prepare(this);
+            }
+
+            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+                    texture.getTarget(), texture.getId(), 0);
+            checkError();
+
+            checkFramebufferStatus();
+        }
+    }
+
+    private static void checkFramebufferStatus() {
+        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+            String msg = "";
+            switch (status) {
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_UNSUPPORTED:
+                    msg = "GL_FRAMEBUFFER_UNSUPPORTED";
+                    break;
+            }
+            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+        }
+    }
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+    }
+
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        int width = texture.getTextureWidth();
+        int height = texture.getTextureHeight();
+        GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+    }
+
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+    }
+
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+    }
+
+    @Override
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, FLOAT_SIZE);
+    }
+
+    @Override
+    public int uploadBuffer(ByteBuffer buf) {
+        return uploadBuffer(buf, 1);
+    }
+
+    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);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void printMatrix(String message, float[] m, int offset) {
+        StringBuilder b = new StringBuilder(message);
+        for (int i = 0; i < MATRIX_SIZE; i++) {
+            b.append(' ');
+            if (i % 4 == 0) {
+                b.append('\n');
+            }
+            b.append(m[offset + i]);
+        }
+        Log.v(TAG, b.toString());
+    }
+
+    @Override
+    public void recoverFromLightCycle() {
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        checkError();
+    }
+
+    @Override
+    public void getBounds(Rect bounds, int x, int y, int width, int height) {
+        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0);
+        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4);
+        bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]);
+        bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]);
+        bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]);
+        bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]);
+        bounds.sort();
+    }
+
+    @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/GLPaint.java b/src/com/android/gallery3d/glrenderer/GLPaint.java
new file mode 100644
index 0000000..16b2206
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -0,0 +1,41 @@
+/*
+ * 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 junit.framework.Assert;
+
+public class GLPaint {
+    private float mLineWidth = 1f;
+    private int mColor = 0;
+
+    public void setColor(int color) {
+        mColor = color;
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+
+    public void setLineWidth(float width) {
+        Assert.assertTrue(width >= 0);
+        mLineWidth = width;
+    }
+
+    public float getLineWidth() {
+        return mLineWidth;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java
new file mode 100644
index 0000000..93f0fdf
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/RawTexture.java
@@ -0,0 +1,73 @@
+/*
+ * 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 javax.microedition.khronos.opengles.GL11;
+
+public class RawTexture extends BasicTexture {
+    private static final String TAG = "RawTexture";
+
+    private final boolean mOpaque;
+    private boolean mIsFlipped;
+
+    public RawTexture(int width, int height, boolean opaque) {
+        mOpaque = opaque;
+        setSize(width, height);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    @Override
+    public boolean isFlippedVertically() {
+        return mIsFlipped;
+    }
+
+    public void setIsFlippedVertically(boolean isFlipped) {
+        mIsFlipped = isFlipped;
+    }
+
+    protected void prepare(GLCanvas canvas) {
+        GLId glId = canvas.getGLId();
+        mId = glId.generateTexture();
+        canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
+        canvas.setTextureParameters(this);
+        mState = STATE_LOADED;
+        setAssociatedCanvas(canvas);
+    }
+
+    @Override
+    protected boolean onBind(GLCanvas canvas) {
+        if (isLoaded()) return true;
+        Log.w(TAG, "lost the content due to context change");
+        return false;
+    }
+
+    @Override
+     public void yield() {
+         // we cannot free the texture because we have no backup.
+     }
+
+    @Override
+    protected int getTarget() {
+        return GL11.GL_TEXTURE_2D;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
new file mode 100644
index 0000000..3dcae4a
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/Texture.java
@@ -0,0 +1,44 @@
+/*
+ * 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
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+//    -- UploadedTexture
+//       -- BitmapTexture
+//       -- Tile
+//       -- ResourceTexture
+//          -- NinePatchTexture
+//       -- CanvasTexture
+//          -- StringTexture
+//
+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);
+    public boolean isOpaque();
+}
diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
new file mode 100644
index 0000000..f41a979
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -0,0 +1,298 @@
+/*
+ * 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 junit.framework.Assert;
+
+import java.util.HashMap;
+
+import javax.microedition.khronos.opengles.GL11;
+
+// 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 BorderKey sBorderKey = new BorderKey();
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "Texture";
+    private boolean mContentValid = true;
+
+    // indicate this textures is being uploaded in background
+    private boolean mIsUploading = false;
+    private boolean mOpaque = true;
+    private boolean mThrottled = false;
+    private static int sUploadedCount;
+    private static final int UPLOAD_LIMIT = 100;
+
+    protected Bitmap mBitmap;
+    private int mBorder;
+
+    protected UploadedTexture() {
+        this(false);
+    }
+
+    protected UploadedTexture(boolean hasBorder) {
+        super(null, 0, STATE_UNLOADED);
+        if (hasBorder) {
+            setBorder(true);
+            mBorder = 1;
+        }
+    }
+
+    protected void setIsUploading(boolean uploading) {
+        mIsUploading = uploading;
+    }
+
+    public boolean isUploading() {
+        return mIsUploading;
+    }
+
+    private static class BorderKey implements Cloneable {
+        public boolean vertical;
+        public Config config;
+        public int length;
+
+        @Override
+        public int hashCode() {
+            int x = config.hashCode() ^ length;
+            return vertical ? x : -x;
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (!(object instanceof BorderKey)) return false;
+            BorderKey o = (BorderKey) object;
+            return vertical == o.vertical
+                    && config == o.config && length == o.length;
+        }
+
+        @Override
+        public BorderKey clone() {
+            try {
+                return (BorderKey) super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    protected void setThrottled(boolean throttled) {
+        mThrottled = throttled;
+    }
+
+    private static Bitmap getBorderLine(
+            boolean vertical, Config config, int length) {
+        BorderKey key = sBorderKey;
+        key.vertical = vertical;
+        key.config = config;
+        key.length = length;
+        Bitmap bitmap = sBorderLines.get(key);
+        if (bitmap == null) {
+            bitmap = vertical
+                    ? Bitmap.createBitmap(1, length, config)
+                    : Bitmap.createBitmap(length, 1, config);
+            sBorderLines.put(key.clone(), bitmap);
+        }
+        return bitmap;
+    }
+
+    private Bitmap getBitmap() {
+        if (mBitmap == null) {
+            mBitmap = onGetBitmap();
+            int w = mBitmap.getWidth() + mBorder * 2;
+            int h = mBitmap.getHeight() + mBorder * 2;
+            if (mWidth == UNSPECIFIED) {
+                setSize(w, h);
+            }
+        }
+        return mBitmap;
+    }
+
+    private void freeBitmap() {
+        Assert.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()) {
+            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
+                return;
+            }
+            uploadToCanvas(canvas);
+        } else if (!mContentValid) {
+            Bitmap bitmap = getBitmap();
+            int format = GLUtils.getInternalFormat(bitmap);
+            int type = GLUtils.getType(bitmap);
+            canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+            freeBitmap();
+            mContentValid = true;
+        }
+    }
+
+    public static void resetUploadLimit() {
+        sUploadedCount = 0;
+    }
+
+    public static boolean uploadLimitReached() {
+        return sUploadedCount > UPLOAD_LIMIT;
+    }
+
+    private void uploadToCanvas(GLCanvas canvas) {
+
+        Bitmap bitmap = getBitmap();
+        if (bitmap != null) {
+            try {
+                int bWidth = bitmap.getWidth();
+                int bHeight = bitmap.getHeight();
+                int width = bWidth + mBorder * 2;
+                int height = bHeight + mBorder * 2;
+                int texWidth = getTextureWidth();
+                int texHeight = getTextureHeight();
+
+                Assert.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, mBorder, mBorder, bitmap, format, type);
+
+                    if (mBorder > 0) {
+                        // Left border
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        canvas.texSubImage2D(this, 0, 0, line, format, type);
+
+                        // Top border
+                        line = getBorderLine(false, config, texWidth);
+                        canvas.texSubImage2D(this, 0, 0, line, format, type);
+                    }
+
+                    // Right border
+                    if (mBorder + bWidth < texWidth) {
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
+                    }
+
+                    // Bottom border
+                    if (mBorder + bHeight < texHeight) {
+                        Bitmap line = getBorderLine(false, config, texWidth);
+                        canvas.texSubImage2D(this, 0, mBorder + 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
+    protected int getTarget() {
+        return GL11.GL_TEXTURE_2D;
+    }
+
+    public void setOpaque(boolean isOpaque) {
+        mOpaque = isOpaque;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        if (mBitmap != null) freeBitmap();
+    }
+}
diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
new file mode 100644
index 0000000..2c4dc2c
--- /dev/null
+++ b/src/com/android/gallery3d/util/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.util;
+
+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/launcher3/AccessibleTabView.java b/src/com/android/launcher3/AccessibleTabView.java
new file mode 100644
index 0000000..90a7865
--- /dev/null
+++ b/src/com/android/launcher3/AccessibleTabView.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+/**
+ * We use a custom tab view to process our own focus traversals.
+ */
+public class AccessibleTabView extends TextView {
+    public AccessibleTabView(Context context) {
+        super(context);
+    }
+
+    public AccessibleTabView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AccessibleTabView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return FocusHelper.handleTabKeyEvent(this, keyCode, event)
+                || super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return FocusHelper.handleTabKeyEvent(this, keyCode, event)
+                || super.onKeyUp(keyCode, event);
+    }
+}
diff --git a/src/com/android/launcher3/AddAdapter.java b/src/com/android/launcher3/AddAdapter.java
new file mode 100644
index 0000000..ad15e75
--- /dev/null
+++ b/src/com/android/launcher3/AddAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import com.android.launcher3.R;
+
+/**
+ * Adapter showing the types of items that can be added to a {@link Workspace}.
+ */
+public class AddAdapter extends BaseAdapter {
+
+    private final LayoutInflater mInflater;
+
+    private final ArrayList<ListItem> mItems = new ArrayList<ListItem>();
+
+    public static final int ITEM_SHORTCUT = 0;
+    public static final int ITEM_APPWIDGET = 1;
+    public static final int ITEM_APPLICATION = 2;
+    public static final int ITEM_WALLPAPER = 3;
+
+    /**
+     * Specific item in our list.
+     */
+    public class ListItem {
+        public final CharSequence text;
+        public final Drawable image;
+        public final int actionTag;
+
+        public ListItem(Resources res, int textResourceId, int imageResourceId, int actionTag) {
+            text = res.getString(textResourceId);
+            if (imageResourceId != -1) {
+                image = res.getDrawable(imageResourceId);
+            } else {
+                image = null;
+            }
+            this.actionTag = actionTag;
+        }
+    }
+    
+    public AddAdapter(Launcher launcher) {
+        super();
+
+        mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        // Create default actions
+        Resources res = launcher.getResources();
+
+        mItems.add(new ListItem(res, R.string.group_wallpapers,
+                R.mipmap.ic_launcher_wallpaper, ITEM_WALLPAPER));
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ListItem item = (ListItem) getItem(position);
+
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.add_list_item, parent, false);
+        }
+
+        TextView textView = (TextView) convertView;
+        textView.setTag(item);
+        textView.setText(item.text);
+        textView.setCompoundDrawablesWithIntrinsicBounds(item.image, null, null, null);
+
+        return convertView;
+    }
+
+    public int getCount() {
+        return mItems.size();
+    }
+
+    public Object getItem(int position) {
+        return mItems.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+}
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
new file mode 100644
index 0000000..91f9bd0
--- /dev/null
+++ b/src/com/android/launcher3/Alarm.java
@@ -0,0 +1,84 @@
+/*
+ * 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.launcher3;
+
+import android.os.Handler;
+
+public class Alarm implements Runnable{
+    // if we reach this time and the alarm hasn't been cancelled, call the listener
+    private long mAlarmTriggerTime;
+
+    // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true.
+    // We use this to avoid having multiple pending callbacks
+    private boolean mWaitingForCallback;
+
+    private Handler mHandler;
+    private OnAlarmListener mAlarmListener;
+    private boolean mAlarmPending = false;
+
+    public Alarm() {
+        mHandler = new Handler();
+    }
+
+    public void setOnAlarmListener(OnAlarmListener alarmListener) {
+        mAlarmListener = alarmListener;
+    }
+
+    // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set,
+    // it's overwritten and only the new alarm setting is used
+    public void setAlarm(long millisecondsInFuture) {
+        long currentTime = System.currentTimeMillis();
+        mAlarmPending = true;
+        mAlarmTriggerTime = currentTime + millisecondsInFuture;
+        if (!mWaitingForCallback) {
+            mHandler.postDelayed(this, mAlarmTriggerTime - currentTime);
+            mWaitingForCallback = true;
+        }
+    }
+
+    public void cancelAlarm() {
+        mAlarmTriggerTime = 0;
+        mAlarmPending = false;
+    }
+
+    // this is called when our timer runs out
+    public void run() {
+        mWaitingForCallback = false;
+        if (mAlarmTriggerTime != 0) {
+            long currentTime = System.currentTimeMillis();
+            if (mAlarmTriggerTime > currentTime) {
+                // We still need to wait some time to trigger spring loaded mode--
+                // post a new callback
+                mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime));
+                mWaitingForCallback = true;
+            } else {
+                mAlarmPending = false;
+                if (mAlarmListener != null) {
+                    mAlarmListener.onAlarm(this);
+                }
+            }
+        }
+    }
+
+    public boolean alarmPending() {
+        return mAlarmPending;
+    }
+}
+
+interface OnAlarmListener {
+    public void onAlarm(Alarm alarm);
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
new file mode 100644
index 0000000..d955e4e
--- /dev/null
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+
+/**
+ * Stores the list of all applications for the all apps view.
+ */
+class AllAppsList {
+    public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
+    
+    /** The list off all apps. */
+    public ArrayList<AppInfo> data =
+            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
+    /** The list of apps that have been added since the last notify() call. */
+    public ArrayList<AppInfo> added =
+            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
+    /** The list of apps that have been removed since the last notify() call. */
+    public ArrayList<AppInfo> removed = new ArrayList<AppInfo>();
+    /** The list of apps that have been modified since the last notify() call. */
+    public ArrayList<AppInfo> modified = new ArrayList<AppInfo>();
+
+    private IconCache mIconCache;
+
+    private AppFilter mAppFilter;
+
+    /**
+     * Boring constructor.
+     */
+    public AllAppsList(IconCache iconCache, AppFilter appFilter) {
+        mIconCache = iconCache;
+        mAppFilter = appFilter;
+    }
+
+    /**
+     * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
+     * list to broadcast when notify() is called.
+     *
+     * If the app is already in the list, doesn't add it.
+     */
+    public void add(AppInfo info) {
+        if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
+            return;
+        }
+        if (findActivity(data, info.componentName)) {
+            return;
+        }
+        data.add(info);
+        added.add(info);
+    }
+
+    public void clear() {
+        data.clear();
+        // TODO: do we clear these too?
+        added.clear();
+        removed.clear();
+        modified.clear();
+    }
+
+    public int size() {
+        return data.size();
+    }
+
+    public AppInfo get(int index) {
+        return data.get(index);
+    }
+
+    /**
+     * Add the icons for the supplied apk called packageName.
+     */
+    public void addPackage(Context context, String packageName) {
+        final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+
+        if (matches.size() > 0) {
+            for (ResolveInfo info : matches) {
+                add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+            }
+        }
+    }
+
+    /**
+     * Remove the apps for the given apk identified by packageName.
+     */
+    public void removePackage(String packageName) {
+        final List<AppInfo> data = this.data;
+        for (int i = data.size() - 1; i >= 0; i--) {
+            AppInfo info = data.get(i);
+            final ComponentName component = info.intent.getComponent();
+            if (packageName.equals(component.getPackageName())) {
+                removed.add(info);
+                data.remove(i);
+            }
+        }
+        // This is more aggressive than it needs to be.
+        mIconCache.flush();
+    }
+
+    /**
+     * Add and remove icons for this package which has been updated.
+     */
+    public void updatePackage(Context context, String packageName) {
+        final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+        if (matches.size() > 0) {
+            // Find disabled/removed activities and remove them from data and add them
+            // to the removed list.
+            for (int i = data.size() - 1; i >= 0; i--) {
+                final AppInfo applicationInfo = data.get(i);
+                final ComponentName component = applicationInfo.intent.getComponent();
+                if (packageName.equals(component.getPackageName())) {
+                    if (!findActivity(matches, component)) {
+                        removed.add(applicationInfo);
+                        mIconCache.remove(component);
+                        data.remove(i);
+                    }
+                }
+            }
+
+            // Find enabled activities and add them to the adapter
+            // Also updates existing activities with new labels/icons
+            int count = matches.size();
+            for (int i = 0; i < count; i++) {
+                final ResolveInfo info = matches.get(i);
+                AppInfo applicationInfo = findApplicationInfoLocked(
+                        info.activityInfo.applicationInfo.packageName,
+                        info.activityInfo.name);
+                if (applicationInfo == null) {
+                    add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+                } else {
+                    mIconCache.remove(applicationInfo.componentName);
+                    mIconCache.getTitleAndIcon(applicationInfo, info, null);
+                    modified.add(applicationInfo);
+                }
+            }
+        } else {
+            // Remove all data for this package.
+            for (int i = data.size() - 1; i >= 0; i--) {
+                final AppInfo applicationInfo = data.get(i);
+                final ComponentName component = applicationInfo.intent.getComponent();
+                if (packageName.equals(component.getPackageName())) {
+                    removed.add(applicationInfo);
+                    mIconCache.remove(component);
+                    data.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Query the package manager for MAIN/LAUNCHER activities in the supplied package.
+     */
+    static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
+        final PackageManager packageManager = context.getPackageManager();
+
+        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        mainIntent.setPackage(packageName);
+
+        final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+        return apps != null ? apps : new ArrayList<ResolveInfo>();
+    }
+
+    /**
+     * Returns whether <em>apps</em> contains <em>component</em>.
+     */
+    private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) {
+        final String className = component.getClassName();
+        for (ResolveInfo info : apps) {
+            final ActivityInfo activityInfo = info.activityInfo;
+            if (activityInfo.name.equals(className)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether <em>apps</em> contains <em>component</em>.
+     */
+    private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component) {
+        final int N = apps.size();
+        for (int i=0; i<N; i++) {
+            final AppInfo info = apps.get(i);
+            if (info.componentName.equals(component)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Find an ApplicationInfo object for the given packageName and className.
+     */
+    private AppInfo findApplicationInfoLocked(String packageName, String className) {
+        for (AppInfo info: data) {
+            final ComponentName component = info.intent.getComponent();
+            if (packageName.equals(component.getPackageName())
+                    && className.equals(component.getClassName())) {
+                return info;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
new file mode 100644
index 0000000..e01436d
--- /dev/null
+++ b/src/com/android/launcher3/AppFilter.java
@@ -0,0 +1,35 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.text.TextUtils;
+import android.util.Log;
+
+public abstract class AppFilter {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "AppFilter";
+
+    public abstract boolean shouldShowApp(ComponentName app);
+
+    public static AppFilter loadByName(String className) {
+        if (TextUtils.isEmpty(className)) return null;
+        if (DBG) Log.d(TAG, "Loading AppFilter: " + className);
+        try {
+            Class<?> cls = Class.forName(className);
+            return (AppFilter) cls.newInstance();
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "Bad AppFilter class", e);
+            return null;
+        } catch (InstantiationException e) {
+            Log.e(TAG, "Bad AppFilter class", e);
+            return null;
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Bad AppFilter class", e);
+            return null;
+        } catch (ClassCastException e) {
+            Log.e(TAG, "Bad AppFilter class", e);
+            return null;
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
new file mode 100644
index 0000000..53f81bb
--- /dev/null
+++ b/src/com/android/launcher3/AppInfo.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Represents an app in AllAppsView.
+ */
+class AppInfo extends ItemInfo {
+    private static final String TAG = "Launcher3.AppInfo";
+
+    /**
+     * The intent used to start the application.
+     */
+    Intent intent;
+
+    /**
+     * A bitmap version of the application icon.
+     */
+    Bitmap iconBitmap;
+
+    /**
+     * The time at which the app was first installed.
+     */
+    long firstInstallTime;
+
+    ComponentName componentName;
+
+    static final int DOWNLOADED_FLAG = 1;
+    static final int UPDATED_SYSTEM_APP_FLAG = 2;
+
+    int flags = 0;
+
+    AppInfo() {
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+    }
+
+    protected Intent getIntent() {
+        return intent;
+    }
+
+    /**
+     * Must not hold the Context.
+     */
+    public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
+            HashMap<Object, CharSequence> labelCache) {
+        final String packageName = info.activityInfo.applicationInfo.packageName;
+
+        this.componentName = new ComponentName(packageName, info.activityInfo.name);
+        this.container = ItemInfo.NO_ID;
+        this.setActivity(componentName,
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+        try {
+            PackageInfo pi = pm.getPackageInfo(packageName, 0);
+            flags = initFlags(pi);
+            firstInstallTime = initFirstInstallTime(pi);
+        } catch (NameNotFoundException e) {
+            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
+        }
+
+        iconCache.getTitleAndIcon(this, info, labelCache);
+    }
+
+    public static int initFlags(PackageInfo pi) {
+        int appFlags = pi.applicationInfo.flags;
+        int flags = 0;
+        if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
+            flags |= DOWNLOADED_FLAG;
+
+            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+                flags |= UPDATED_SYSTEM_APP_FLAG;
+            }
+        }
+        return flags;
+    }
+
+    public static long initFirstInstallTime(PackageInfo pi) {
+        return pi.firstInstallTime;
+    }
+
+    public AppInfo(AppInfo info) {
+        super(info);
+        componentName = info.componentName;
+        title = info.title.toString();
+        intent = new Intent(info.intent);
+        flags = info.flags;
+        firstInstallTime = info.firstInstallTime;
+    }
+
+    /**
+     * Creates the application intent based on a component name and various launch flags.
+     * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
+     *
+     * @param className the class name of the component representing the intent
+     * @param launchFlags the launch flags
+     */
+    final void setActivity(ComponentName className, int launchFlags) {
+        intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setComponent(className);
+        intent.setFlags(launchFlags);
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+    }
+
+    @Override
+    public String toString() {
+        return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+                + " type=" + this.itemType + " container=" + this.container
+                + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
+                + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+    }
+
+    public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
+        Log.d(tag, label + " size=" + list.size());
+        for (AppInfo info: list) {
+            Log.d(tag, "   title=\"" + info.title + "\" iconBitmap="
+                    + info.iconBitmap + " firstInstallTime="
+                    + info.firstInstallTime);
+        }
+    }
+
+    public ShortcutInfo makeShortcut() {
+        return new ShortcutInfo(this);
+    }
+}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
new file mode 100644
index 0000000..fcb04ea
--- /dev/null
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -0,0 +1,469 @@
+package com.android.launcher3;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+public class AppWidgetResizeFrame extends FrameLayout {
+    private LauncherAppWidgetHostView mWidgetView;
+    private CellLayout mCellLayout;
+    private DragLayer mDragLayer;
+    private ImageView mLeftHandle;
+    private ImageView mRightHandle;
+    private ImageView mTopHandle;
+    private ImageView mBottomHandle;
+
+    private boolean mLeftBorderActive;
+    private boolean mRightBorderActive;
+    private boolean mTopBorderActive;
+    private boolean mBottomBorderActive;
+
+    private int mWidgetPaddingLeft;
+    private int mWidgetPaddingRight;
+    private int mWidgetPaddingTop;
+    private int mWidgetPaddingBottom;
+
+    private int mBaselineWidth;
+    private int mBaselineHeight;
+    private int mBaselineX;
+    private int mBaselineY;
+    private int mResizeMode;
+
+    private int mRunningHInc;
+    private int mRunningVInc;
+    private int mMinHSpan;
+    private int mMinVSpan;
+    private int mDeltaX;
+    private int mDeltaY;
+    private int mDeltaXAddOn;
+    private int mDeltaYAddOn;
+
+    private int mBackgroundPadding;
+    private int mTouchTargetWidth;
+
+    private int mTopTouchRegionAdjustment = 0;
+    private int mBottomTouchRegionAdjustment = 0;
+
+    int[] mDirectionVector = new int[2];
+    int[] mLastDirectionVector = new int[2];
+    int[] mTmpPt = new int[2];
+
+    final int SNAP_DURATION = 150;
+    final int BACKGROUND_PADDING = 24;
+    final float DIMMED_HANDLE_ALPHA = 0f;
+    final float RESIZE_THRESHOLD = 0.66f;
+
+    private static Rect mTmpRect = new Rect();
+
+    public static final int LEFT = 0;
+    public static final int TOP = 1;
+    public static final int RIGHT = 2;
+    public static final int BOTTOM = 3;
+
+    private Launcher mLauncher;
+
+    public AppWidgetResizeFrame(Context context,
+            LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
+
+        super(context);
+        mLauncher = (Launcher) context;
+        mCellLayout = cellLayout;
+        mWidgetView = widgetView;
+        mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
+        mDragLayer = dragLayer;
+
+        final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
+        int[] result = Launcher.getMinSpanForWidget(mLauncher, info);
+        mMinHSpan = result[0];
+        mMinVSpan = result[1];
+
+        setBackgroundResource(R.drawable.widget_resize_frame_holo);
+        setPadding(0, 0, 0, 0);
+
+        LayoutParams lp;
+        mLeftHandle = new ImageView(context);
+        mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
+        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 
+                Gravity.LEFT | Gravity.CENTER_VERTICAL);
+        addView(mLeftHandle, lp);
+
+        mRightHandle = new ImageView(context);
+        mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
+        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 
+                Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+        addView(mRightHandle, lp);
+
+        mTopHandle = new ImageView(context);
+        mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
+        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 
+                Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+        addView(mTopHandle, lp);
+
+        mBottomHandle = new ImageView(context);
+        mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
+        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 
+                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+        addView(mBottomHandle, lp);
+
+        Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context,
+                widgetView.getAppWidgetInfo().provider, null);
+        mWidgetPaddingLeft = p.left;
+        mWidgetPaddingTop = p.top;
+        mWidgetPaddingRight = p.right;
+        mWidgetPaddingBottom = p.bottom;
+
+        if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
+            mTopHandle.setVisibility(GONE);
+            mBottomHandle.setVisibility(GONE);
+        } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
+            mLeftHandle.setVisibility(GONE);
+            mRightHandle.setVisibility(GONE);
+        }
+
+        final float density = mLauncher.getResources().getDisplayMetrics().density;
+        mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
+        mTouchTargetWidth = 2 * mBackgroundPadding;
+
+        // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
+        // cells (same if not resized, or different) will be marked as occupied when the resize
+        // frame is dismissed.
+        mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
+    }
+
+    public boolean beginResizeIfPointInRegion(int x, int y) {
+        boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
+        boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+
+        mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
+        mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
+        mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
+        mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
+                && verticalActive;
+
+        boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
+                || mTopBorderActive || mBottomBorderActive;
+
+        mBaselineWidth = getMeasuredWidth();
+        mBaselineHeight = getMeasuredHeight();
+        mBaselineX = getLeft();
+        mBaselineY = getTop();
+
+        if (anyBordersActive) {
+            mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+            mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
+            mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+            mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+        }
+        return anyBordersActive;
+    }
+
+    /**
+     *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
+     *  of the CellLayout, and such that the frame's borders can't cross.
+     */
+    public void updateDeltas(int deltaX, int deltaY) {
+        if (mLeftBorderActive) {
+            mDeltaX = Math.max(-mBaselineX, deltaX); 
+            mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
+        } else if (mRightBorderActive) {
+            mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
+            mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
+        }
+
+        if (mTopBorderActive) {
+            mDeltaY = Math.max(-mBaselineY, deltaY);
+            mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
+        } else if (mBottomBorderActive) {
+            mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
+            mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
+        }
+    }
+
+    public void visualizeResizeForDelta(int deltaX, int deltaY) {
+        visualizeResizeForDelta(deltaX, deltaY, false);
+    }
+
+    /**
+     *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
+     */
+    private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
+        updateDeltas(deltaX, deltaY);
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+
+        if (mLeftBorderActive) {
+            lp.x = mBaselineX + mDeltaX;
+            lp.width = mBaselineWidth - mDeltaX;
+        } else if (mRightBorderActive) {
+            lp.width = mBaselineWidth + mDeltaX;
+        }
+
+        if (mTopBorderActive) {
+            lp.y = mBaselineY + mDeltaY;
+            lp.height = mBaselineHeight - mDeltaY;
+        } else if (mBottomBorderActive) {
+            lp.height = mBaselineHeight + mDeltaY;
+        }
+
+        resizeWidgetIfNeeded(onDismiss);
+        requestLayout();
+    }
+
+    /**
+     *  Based on the current deltas, we determine if and how to resize the widget.
+     */
+    private void resizeWidgetIfNeeded(boolean onDismiss) {
+        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
+        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+
+        int deltaX = mDeltaX + mDeltaXAddOn;
+        int deltaY = mDeltaY + mDeltaYAddOn;
+
+        float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
+        float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
+
+        int hSpanInc = 0;
+        int vSpanInc = 0;
+        int cellXInc = 0;
+        int cellYInc = 0;
+
+        int countX = mCellLayout.getCountX();
+        int countY = mCellLayout.getCountY();
+
+        if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
+            hSpanInc = Math.round(hSpanIncF);
+        }
+        if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
+            vSpanInc = Math.round(vSpanIncF);
+        }
+
+        if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
+
+
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
+
+        int spanX = lp.cellHSpan;
+        int spanY = lp.cellVSpan;
+        int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
+        int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
+
+        int hSpanDelta = 0;
+        int vSpanDelta = 0;
+
+        // For each border, we bound the resizing based on the minimum width, and the maximum
+        // expandability.
+        if (mLeftBorderActive) {
+            cellXInc = Math.max(-cellX, hSpanInc);
+            cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
+            hSpanInc *= -1;
+            hSpanInc = Math.min(cellX, hSpanInc);
+            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
+            hSpanDelta = -hSpanInc;
+
+        } else if (mRightBorderActive) {
+            hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
+            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
+            hSpanDelta = hSpanInc;
+        }
+
+        if (mTopBorderActive) {
+            cellYInc = Math.max(-cellY, vSpanInc);
+            cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
+            vSpanInc *= -1;
+            vSpanInc = Math.min(cellY, vSpanInc);
+            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
+            vSpanDelta = -vSpanInc;
+        } else if (mBottomBorderActive) {
+            vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
+            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
+            vSpanDelta = vSpanInc;
+        }
+
+        mDirectionVector[0] = 0;
+        mDirectionVector[1] = 0;
+        // Update the widget's dimensions and position according to the deltas computed above
+        if (mLeftBorderActive || mRightBorderActive) {
+            spanX += hSpanInc;
+            cellX += cellXInc;
+            if (hSpanDelta != 0) {
+                mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
+            }
+        }
+
+        if (mTopBorderActive || mBottomBorderActive) {
+            spanY += vSpanInc;
+            cellY += cellYInc;
+            if (vSpanDelta != 0) {
+                mDirectionVector[1] = mTopBorderActive ? -1 : 1;
+            }
+        }
+
+        if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
+
+        // We always want the final commit to match the feedback, so we make sure to use the
+        // last used direction vector when committing the resize / reorder.
+        if (onDismiss) {
+            mDirectionVector[0] = mLastDirectionVector[0];
+            mDirectionVector[1] = mLastDirectionVector[1];
+        } else {
+            mLastDirectionVector[0] = mDirectionVector[0];
+            mLastDirectionVector[1] = mDirectionVector[1];
+        }
+
+        if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
+                mDirectionVector, onDismiss)) {
+            lp.tmpCellX = cellX;
+            lp.tmpCellY = cellY;
+            lp.cellHSpan = spanX;
+            lp.cellVSpan = spanY;
+            mRunningVInc += vSpanDelta;
+            mRunningHInc += hSpanDelta;
+            if (!onDismiss) {
+                updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
+            }
+        }
+        mWidgetView.requestLayout();
+    }
+
+    static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
+            int spanX, int spanY) {
+
+        getWidgetSizeRanges(launcher, spanX, spanY, mTmpRect);
+        widgetView.updateAppWidgetSize(null, mTmpRect.left, mTmpRect.top,
+                mTmpRect.right, mTmpRect.bottom);
+    }
+
+    static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
+        if (rect == null) {
+            rect = new Rect();
+        }
+        Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE);
+        Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT);
+        final float density = launcher.getResources().getDisplayMetrics().density;
+
+        // Compute landscape size
+        int cellWidth = landMetrics.left;
+        int cellHeight = landMetrics.top;
+        int widthGap = landMetrics.right;
+        int heightGap = landMetrics.bottom;
+        int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
+        int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+
+        // Compute portrait size
+        cellWidth = portMetrics.left;
+        cellHeight = portMetrics.top;
+        widthGap = portMetrics.right;
+        heightGap = portMetrics.bottom;
+        int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
+        int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+        rect.set(portWidth, landHeight, landWidth, portHeight);
+        return rect;
+    }
+
+    /**
+     * This is the final step of the resize. Here we save the new widget size and position
+     * to LauncherModel and animate the resize frame.
+     */
+    public void commitResize() {
+        resizeWidgetIfNeeded(true);
+        requestLayout();
+    }
+
+    public void onTouchUp() {
+        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
+        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+
+        mDeltaXAddOn = mRunningHInc * xThreshold; 
+        mDeltaYAddOn = mRunningVInc * yThreshold; 
+        mDeltaX = 0;
+        mDeltaY = 0;
+
+        post(new Runnable() {
+            @Override
+            public void run() {
+                snapToWidget(true);
+            }
+        });
+    }
+
+    public void snapToWidget(boolean animate) {
+        final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
+                mWidgetPaddingRight;
+        int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
+                mWidgetPaddingBottom;
+
+        mTmpPt[0] = mWidgetView.getLeft();
+        mTmpPt[1] = mWidgetView.getTop();
+        mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
+
+        int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPaddingLeft;
+        int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPaddingTop;
+
+        // We need to make sure the frame's touchable regions lie fully within the bounds of the 
+        // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
+        // down accordingly to provide a proper touch target.
+        if (newY < 0) {
+            // In this case we shift the touch region down to start at the top of the DragLayer
+            mTopTouchRegionAdjustment = -newY;
+        } else {
+            mTopTouchRegionAdjustment = 0;
+        }
+        if (newY + newHeight > mDragLayer.getHeight()) {
+            // In this case we shift the touch region up to end at the bottom of the DragLayer
+            mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
+        } else {
+            mBottomTouchRegionAdjustment = 0;
+        }
+
+        if (!animate) {
+            lp.width = newWidth;
+            lp.height = newHeight;
+            lp.x = newX;
+            lp.y = newY;
+            mLeftHandle.setAlpha(1.0f);
+            mRightHandle.setAlpha(1.0f);
+            mTopHandle.setAlpha(1.0f);
+            mBottomHandle.setAlpha(1.0f);
+            requestLayout();
+        } else {
+            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
+            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
+                    newHeight);
+            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
+            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
+            ObjectAnimator oa =
+                    LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
+            ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f);
+            ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f);
+            ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f);
+            ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f);
+            oa.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    requestLayout();
+                }
+            });
+            AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
+            if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
+                set.playTogether(oa, topOa, bottomOa);
+            } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
+                set.playTogether(oa, leftOa, rightOa);
+            } else {
+                set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
+            }
+
+            set.setDuration(SNAP_DURATION);
+            set.start();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/AppsCustomizeCellLayout.java b/src/com/android/launcher3/AppsCustomizeCellLayout.java
new file mode 100644
index 0000000..3c8bda9
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizeCellLayout.java
@@ -0,0 +1,63 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.view.View;
+
+public class AppsCustomizeCellLayout extends CellLayout implements Page {
+    public AppsCustomizeCellLayout(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void removeAllViewsOnPage() {
+        removeAllViews();
+        setLayerType(LAYER_TYPE_NONE, null);
+    }
+
+    @Override
+    public void removeViewOnPageAt(int index) {
+        removeViewAt(index);
+    }
+
+    @Override
+    public int getPageChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getChildOnPageAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public int indexOfChildOnPage(View v) {
+        return indexOfChild(v);
+    }
+
+    /**
+     * Clears all the key listeners for the individual icons.
+     */
+    public void resetChildrenOnKeyListeners() {
+        ShortcutAndWidgetContainer children = getShortcutsAndWidgets();
+        int childCount = children.getChildCount();
+        for (int j = 0; j < childCount; ++j) {
+            children.getChildAt(j).setOnKeyListener(null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
new file mode 100644
index 0000000..9b35bb5
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -0,0 +1,1693 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A simple callback interface which also provides the results of the task.
+ */
+interface AsyncTaskCallback {
+    void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
+}
+
+/**
+ * The data needed to perform either of the custom AsyncTasks.
+ */
+class AsyncTaskPageData {
+    enum Type {
+        LoadWidgetPreviewData
+    }
+
+    AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
+            AsyncTaskCallback postR, WidgetPreviewLoader w) {
+        page = p;
+        items = l;
+        generatedImages = new ArrayList<Bitmap>();
+        maxImageWidth = cw;
+        maxImageHeight = ch;
+        doInBackgroundCallback = bgR;
+        postExecuteCallback = postR;
+        widgetPreviewLoader = w;
+    }
+    void cleanup(boolean cancelled) {
+        // Clean up any references to source/generated bitmaps
+        if (generatedImages != null) {
+            if (cancelled) {
+                for (int i = 0; i < generatedImages.size(); i++) {
+                    widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
+                }
+            }
+            generatedImages.clear();
+        }
+    }
+    int page;
+    ArrayList<Object> items;
+    ArrayList<Bitmap> sourceImages;
+    ArrayList<Bitmap> generatedImages;
+    int maxImageWidth;
+    int maxImageHeight;
+    AsyncTaskCallback doInBackgroundCallback;
+    AsyncTaskCallback postExecuteCallback;
+    WidgetPreviewLoader widgetPreviewLoader;
+}
+
+/**
+ * A generic template for an async task used in AppsCustomize.
+ */
+class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
+    AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
+        page = p;
+        threadPriority = Process.THREAD_PRIORITY_DEFAULT;
+        dataType = ty;
+    }
+    @Override
+    protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
+        if (params.length != 1) return null;
+        // Load each of the widget previews in the background
+        params[0].doInBackgroundCallback.run(this, params[0]);
+        return params[0];
+    }
+    @Override
+    protected void onPostExecute(AsyncTaskPageData result) {
+        // All the widget previews are loaded, so we can just callback to inflate the page
+        result.postExecuteCallback.run(this, result);
+    }
+
+    void setThreadPriority(int p) {
+        threadPriority = p;
+    }
+    void syncThreadPriority() {
+        Process.setThreadPriority(threadPriority);
+    }
+
+    // The page that this async task is associated with
+    AsyncTaskPageData.Type dataType;
+    int page;
+    int threadPriority;
+}
+
+/**
+ * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
+ */
+public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
+        View.OnClickListener, View.OnKeyListener, DragSource,
+        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
+        LauncherTransitionable {
+    static final String TAG = "AppsCustomizePagedView";
+
+    /**
+     * The different content types that this paged view can show.
+     */
+    public enum ContentType {
+        Applications,
+        Widgets
+    }
+    private ContentType mContentType = ContentType.Applications;
+
+    // Refs
+    private Launcher mLauncher;
+    private DragController mDragController;
+    private final LayoutInflater mLayoutInflater;
+    private final PackageManager mPackageManager;
+
+    // Save and Restore
+    private int mSaveInstanceStateItemIndex = -1;
+    private PagedViewIcon mPressedIcon;
+
+    // Content
+    private ArrayList<AppInfo> mApps;
+    private ArrayList<Object> mWidgets;
+
+    // Cling
+    private boolean mHasShownAllAppsCling;
+    private int mClingFocusedX;
+    private int mClingFocusedY;
+
+    // Caching
+    private Canvas mCanvas;
+    private IconCache mIconCache;
+
+    // Dimens
+    private int mContentWidth, mContentHeight;
+    private int mWidgetCountX, mWidgetCountY;
+    private int mWidgetWidthGap, mWidgetHeightGap;
+    private PagedViewCellLayout mWidgetSpacingLayout;
+    private int mNumAppsPages;
+    private int mNumWidgetPages;
+
+    // Relating to the scroll and overscroll effects
+    Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
+    private static float CAMERA_DISTANCE = 6500;
+    private static float TRANSITION_SCALE_FACTOR = 0.74f;
+    private static float TRANSITION_PIVOT = 0.65f;
+    private static float TRANSITION_MAX_ROTATION = 22;
+    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
+    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
+    private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+
+    public static boolean DISABLE_ALL_APPS = false;
+
+    // Previews & outlines
+    ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
+    private static final int sPageSleepDelay = 200;
+
+    private Runnable mInflateWidgetRunnable = null;
+    private Runnable mBindWidgetRunnable = null;
+    static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
+    static final int WIDGET_PRELOAD_PENDING = 0;
+    static final int WIDGET_BOUND = 1;
+    static final int WIDGET_INFLATED = 2;
+    int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+    int mWidgetLoadingId = -1;
+    PendingAddWidgetInfo mCreateWidgetInfo = null;
+    private boolean mDraggingWidget = false;
+
+    private Toast mWidgetInstructionToast;
+
+    // Deferral of loading widget previews during launcher transitions
+    private boolean mInTransition;
+    private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
+        new ArrayList<AsyncTaskPageData>();
+    private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
+        new ArrayList<Runnable>();
+
+    private Rect mTmpRect = new Rect();
+
+    // Used for drawing shortcut previews
+    BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
+    PaintCache mCachedShortcutPreviewPaint = new PaintCache();
+    CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
+
+    // Used for drawing widget previews
+    CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
+    RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
+    RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
+    PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
+
+    WidgetPreviewLoader mWidgetPreviewLoader;
+
+    private boolean mInBulkBind;
+    private boolean mNeedToUpdatePageCountsAndInvalidateData;
+
+    public AppsCustomizePagedView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mLayoutInflater = LayoutInflater.from(context);
+        mPackageManager = context.getPackageManager();
+        mApps = new ArrayList<AppInfo>();
+        mWidgets = new ArrayList<Object>();
+        mIconCache = (LauncherAppState.getInstance()).getIconCache();
+        mCanvas = new Canvas();
+        mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
+
+        // Save the default widget preview background
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mWidgetWidthGap = mWidgetHeightGap = grid.edgeMarginPx;
+        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
+        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
+        mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
+        mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
+        a.recycle();
+        mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
+
+        // The padding on the non-matched dimension for the default widget preview icons
+        // (top + bottom)
+        mFadeInAdjacentScreens = false;
+
+        // Unless otherwise specified this view is important for accessibility.
+        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        mCenterPagesVertically = false;
+
+        Context context = getContext();
+        Resources r = context.getResources();
+        setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
+    }
+
+    public void onFinishInflate() {
+        super.onFinishInflate();
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        setPadding(grid.edgeMarginPx, 2 * grid.edgeMarginPx,
+                grid.edgeMarginPx, 2 * grid.edgeMarginPx);
+    }
+
+    /** Returns the item index of the center item on this page so that we can restore to this
+     *  item index when we rotate. */
+    private int getMiddleComponentIndexOnCurrentPage() {
+        int i = -1;
+        if (getPageCount() > 0) {
+            int currentPage = getCurrentPage();
+            if (mContentType == ContentType.Applications) {
+                AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(currentPage);
+                ShortcutAndWidgetContainer childrenLayout = layout.getShortcutsAndWidgets();
+                int numItemsPerPage = mCellCountX * mCellCountY;
+                int childCount = childrenLayout.getChildCount();
+                if (childCount > 0) {
+                    i = (currentPage * numItemsPerPage) + (childCount / 2);
+                }
+            } else if (mContentType == ContentType.Widgets) {
+                int numApps = mApps.size();
+                PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
+                int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+                int childCount = layout.getChildCount();
+                if (childCount > 0) {
+                    i = numApps +
+                        (currentPage * numItemsPerPage) + (childCount / 2);
+                }
+            } else {
+                throw new RuntimeException("Invalid ContentType");
+            }
+        }
+        return i;
+    }
+
+    /** Get the index of the item to restore to if we need to restore the current page. */
+    int getSaveInstanceStateIndex() {
+        if (mSaveInstanceStateItemIndex == -1) {
+            mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
+        }
+        return mSaveInstanceStateItemIndex;
+    }
+
+    /** Returns the page in the current orientation which is expected to contain the specified
+     *  item index. */
+    int getPageForComponent(int index) {
+        if (index < 0) return 0;
+
+        if (index < mApps.size()) {
+            int numItemsPerPage = mCellCountX * mCellCountY;
+            return (index / numItemsPerPage);
+        } else {
+            int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+            return (index - mApps.size()) / numItemsPerPage;
+        }
+    }
+
+    /** Restores the page for an item at the specified index */
+    void restorePageForIndex(int index) {
+        if (index < 0) return;
+        mSaveInstanceStateItemIndex = index;
+    }
+
+    private void updatePageCounts() {
+        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
+                (float) (mWidgetCountX * mWidgetCountY));
+        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
+    }
+
+    protected void onDataReady(int width, int height) {
+        if (mWidgetPreviewLoader == null) {
+            mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
+        }
+
+        // Now that the data is ready, we can calculate the content width, the number of cells to
+        // use for each page
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+                mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+        mCellCountX = (int) grid.allAppsNumCols;
+        mCellCountY = (int) grid.allAppsNumRows;
+        updatePageCounts();
+
+        // Force a measure to update recalculate the gaps
+        mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+        mContentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+        int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+        int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+        mWidgetSpacingLayout.measure(widthSpec, heightSpec);
+
+        AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost();
+        final boolean hostIsTransitioning = host.isTransitioning();
+
+        // Restore the page
+        int page = getPageForComponent(mSaveInstanceStateItemIndex);
+        invalidatePageData(Math.max(0, page), hostIsTransitioning);
+
+        // Show All Apps cling if we are finished transitioning, otherwise, we will try again when
+        // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be
+        // returned while animating)
+        if (!hostIsTransitioning) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    showAllAppsCling();
+                }
+            });
+        }
+    }
+
+    void showAllAppsCling() {
+        if (!mHasShownAllAppsCling && isDataReady()) {
+            mHasShownAllAppsCling = true;
+            // Calculate the position for the cling punch through
+            int[] offset = new int[2];
+            int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
+            mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
+            // PagedViews are centered horizontally but top aligned
+            // Note we have to shift the items up now that Launcher sits under the status bar
+            pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
+                    offset[0];
+            pos[1] += offset[1] - mLauncher.getDragLayer().getPaddingTop();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        if (!isDataReady()) {
+            if ((DISABLE_ALL_APPS || !mApps.isEmpty()) && !mWidgets.isEmpty()) {
+                setDataIsReady();
+                setMeasuredDimension(width, height);
+                onDataReady(width, height);
+            }
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        // Get the list of widgets and shortcuts
+        mWidgets.clear();
+        for (Object o : widgetsAndShortcuts) {
+            if (o instanceof AppWidgetProviderInfo) {
+                AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o;
+                if (!app.shouldShowAppOrWidgetProvider(widget.provider)) {
+                    continue;
+                }
+                widget.label = widget.label.trim();
+                if (widget.minWidth > 0 && widget.minHeight > 0) {
+                    // Ensure that all widgets we show can be added on a workspace of this size
+                    int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
+                    int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
+                    int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
+                    int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
+                    if (minSpanX <= (int) grid.numColumns &&
+                        minSpanY <= (int) grid.numRows) {
+                        mWidgets.add(widget);
+                    } else {
+                        Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
+                              widget.minWidth + ", " + widget.minHeight + ")");
+                    }
+                } else {
+                    Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
+                          widget.minWidth + ", " + widget.minHeight + ")");
+                }
+            } else {
+                // just add shortcuts
+                mWidgets.add(o);
+            }
+        }
+        updatePageCountsAndInvalidateData();
+    }
+
+    public void setBulkBind(boolean bulkBind) {
+        if (bulkBind) {
+            mInBulkBind = true;
+        } else {
+            mInBulkBind = false;
+            if (mNeedToUpdatePageCountsAndInvalidateData) {
+                updatePageCountsAndInvalidateData();
+            }
+        }
+    }
+
+    private void updatePageCountsAndInvalidateData() {
+        if (mInBulkBind) {
+            mNeedToUpdatePageCountsAndInvalidateData = true;
+        } else {
+            updatePageCounts();
+            invalidateOnDataChange();
+            mNeedToUpdatePageCountsAndInvalidateData = false;
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        // When we have exited all apps or are in transition, disregard clicks
+        if (!mLauncher.isAllAppsVisible() ||
+                mLauncher.getWorkspace().isSwitchingState()) return;
+
+        if (v instanceof PagedViewIcon) {
+            // Animate some feedback to the click
+            final AppInfo appInfo = (AppInfo) v.getTag();
+
+            // Lock the drawable state to pressed until we return to Launcher
+            if (mPressedIcon != null) {
+                mPressedIcon.lockDrawableState();
+            }
+            mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
+            mLauncher.getStats().recordLaunch(appInfo.intent);
+        } else if (v instanceof PagedViewWidget) {
+            // Let the user know that they have to long press to add a widget
+            if (mWidgetInstructionToast != null) {
+                mWidgetInstructionToast.cancel();
+            }
+            mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
+                Toast.LENGTH_SHORT);
+            mWidgetInstructionToast.show();
+
+            // Create a little animation to show that the widget can move
+            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
+            AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
+            ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
+            tyuAnim.setDuration(125);
+            ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
+            tydAnim.setDuration(100);
+            bounce.play(tyuAnim).before(tydAnim);
+            bounce.setInterpolator(new AccelerateInterpolator());
+            bounce.start();
+        }
+    }
+
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        return FocusHelper.handleAppsCustomizeKeyEvent(v,  keyCode, event);
+    }
+
+    /*
+     * PagedViewWithDraggableItems implementation
+     */
+    @Override
+    protected void determineDraggingStart(android.view.MotionEvent ev) {
+        // Disable dragging by pulling an app down for now.
+    }
+
+    private void beginDraggingApplication(View v) {
+        mLauncher.getWorkspace().onDragStartedWithItem(v);
+        mLauncher.getWorkspace().beginDragShared(v, this);
+    }
+
+    Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
+        Bundle options = null;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect);
+            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher,
+                    info.componentName, null);
+
+            float density = getResources().getDisplayMetrics().density;
+            int xPaddingDips = (int) ((padding.left + padding.right) / density);
+            int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+
+            options = new Bundle();
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+                    mTmpRect.left - xPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+                    mTmpRect.top - yPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+                    mTmpRect.right - xPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+                    mTmpRect.bottom - yPaddingDips);
+        }
+        return options;
+    }
+
+    private void preloadWidget(final PendingAddWidgetInfo info) {
+        final AppWidgetProviderInfo pInfo = info.info;
+        final Bundle options = getDefaultOptionsForWidget(mLauncher, info);
+
+        if (pInfo.configure != null) {
+            info.bindOptions = options;
+            return;
+        }
+
+        mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
+        mBindWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+                // Options will be null for platforms with JB or lower, so this serves as an
+                // SDK level check.
+                if (options == null) {
+                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+                            mWidgetLoadingId, info.componentName)) {
+                        mWidgetCleanupState = WIDGET_BOUND;
+                    }
+                } else {
+                    if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+                            mWidgetLoadingId, info.componentName, options)) {
+                        mWidgetCleanupState = WIDGET_BOUND;
+                    }
+                }
+            }
+        };
+        post(mBindWidgetRunnable);
+
+        mInflateWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mWidgetCleanupState != WIDGET_BOUND) {
+                    return;
+                }
+                AppWidgetHostView hostView = mLauncher.
+                        getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
+                info.boundWidget = hostView;
+                mWidgetCleanupState = WIDGET_INFLATED;
+                hostView.setVisibility(INVISIBLE);
+                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
+                        info.spanY, info, false);
+
+                // We want the first widget layout to be the correct size. This will be important
+                // for width size reporting to the AppWidgetManager.
+                DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
+                        unScaledSize[1]);
+                lp.x = lp.y = 0;
+                lp.customPosition = true;
+                hostView.setLayoutParams(lp);
+                mLauncher.getDragLayer().addView(hostView);
+            }
+        };
+        post(mInflateWidgetRunnable);
+    }
+
+    @Override
+    public void onShortPress(View v) {
+        // We are anticipating a long press, and we use this time to load bind and instantiate
+        // the widget. This will need to be cleaned up if it turns out no long press occurs.
+        if (mCreateWidgetInfo != null) {
+            // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
+            cleanupWidgetPreloading(false);
+        }
+        mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
+        preloadWidget(mCreateWidgetInfo);
+    }
+
+    private void cleanupWidgetPreloading(boolean widgetWasAdded) {
+        if (!widgetWasAdded) {
+            // If the widget was not added, we may need to do further cleanup.
+            PendingAddWidgetInfo info = mCreateWidgetInfo;
+            mCreateWidgetInfo = null;
+
+            if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
+                // We never did any preloading, so just remove pending callbacks to do so
+                removeCallbacks(mBindWidgetRunnable);
+                removeCallbacks(mInflateWidgetRunnable);
+            } else if (mWidgetCleanupState == WIDGET_BOUND) {
+                 // Delete the widget id which was allocated
+                if (mWidgetLoadingId != -1) {
+                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+                }
+
+                // We never got around to inflating the widget, so remove the callback to do so.
+                removeCallbacks(mInflateWidgetRunnable);
+            } else if (mWidgetCleanupState == WIDGET_INFLATED) {
+                // Delete the widget id which was allocated
+                if (mWidgetLoadingId != -1) {
+                    mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+                }
+
+                // The widget was inflated and added to the DragLayer -- remove it.
+                AppWidgetHostView widget = info.boundWidget;
+                mLauncher.getDragLayer().removeView(widget);
+            }
+        }
+        mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+        mWidgetLoadingId = -1;
+        mCreateWidgetInfo = null;
+        PagedViewWidget.resetShortPressTarget();
+    }
+
+    @Override
+    public void cleanUpShortPress(View v) {
+        if (!mDraggingWidget) {
+            cleanupWidgetPreloading(false);
+        }
+    }
+
+    private boolean beginDraggingWidget(View v) {
+        mDraggingWidget = true;
+        // Get the widget preview as the drag representation
+        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
+        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
+
+        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+        // we abort the drag.
+        if (image.getDrawable() == null) {
+            mDraggingWidget = false;
+            return false;
+        }
+
+        // Compose the drag image
+        Bitmap preview;
+        Bitmap outline;
+        float scale = 1f;
+        Point previewPadding = null;
+
+        if (createItemInfo instanceof PendingAddWidgetInfo) {
+            // This can happen in some weird cases involving multi-touch. We can't start dragging
+            // the widget if this is null, so we break out.
+            if (mCreateWidgetInfo == null) {
+                return false;
+            }
+
+            PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
+            createItemInfo = createWidgetInfo;
+            int spanX = createItemInfo.spanX;
+            int spanY = createItemInfo.spanY;
+            int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
+                    createWidgetInfo, true);
+
+            FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+            float minScale = 1.25f;
+            int maxWidth, maxHeight;
+            maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+            maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
+
+            int[] previewSizeBeforeScale = new int[1];
+
+            preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName,
+                    createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY,
+                    maxWidth, maxHeight, null, previewSizeBeforeScale);
+
+            // Compare the size of the drag preview to the preview in the AppsCustomize tray
+            int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
+                    mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX));
+            scale = previewWidthInAppsCustomize / (float) preview.getWidth();
+
+            // The bitmap in the AppsCustomize tray is always the the same size, so there
+            // might be extra pixels around the preview itself - this accounts for that
+            if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
+                int padding =
+                        (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
+                previewPadding = new Point(padding, 0);
+            }
+        } else {
+            PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
+            Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
+            preview = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+            mCanvas.setBitmap(preview);
+            mCanvas.save();
+            WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0,
+                    icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+            mCanvas.restore();
+            mCanvas.setBitmap(null);
+            createItemInfo.spanX = createItemInfo.spanY = 1;
+        }
+
+        // Don't clip alpha values for the drag outline if we're using the default widget preview
+        boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
+                (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
+
+        // Save the preview for the outline generation, then dim the preview
+        outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
+                false);
+
+        // Start the drag
+        mLauncher.lockScreenOrientation();
+        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
+        mDragController.startDrag(image, preview, this, createItemInfo,
+                DragController.DRAG_ACTION_COPY, previewPadding, scale);
+        outline.recycle();
+        preview.recycle();
+        return true;
+    }
+
+    @Override
+    protected boolean beginDragging(final View v) {
+        if (!super.beginDragging(v)) return false;
+
+        if (v instanceof PagedViewIcon) {
+            beginDraggingApplication(v);
+        } else if (v instanceof PagedViewWidget) {
+            if (!beginDraggingWidget(v)) {
+                return false;
+            }
+        }
+
+        // We delay entering spring-loaded mode slightly to make sure the UI
+        // thready is free of any work.
+        postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                // We don't enter spring-loaded mode if the drag has been cancelled
+                if (mLauncher.getDragController().isDragging()) {
+                    // Reset the alpha on the dragged icon before we drag
+                    resetDrawableState();
+
+                    // Go into spring loaded mode (must happen before we startDrag())
+                    mLauncher.enterSpringLoadedDragMode();
+                }
+            }
+        }, 150);
+
+        return true;
+    }
+
+    /**
+     * Clean up after dragging.
+     *
+     * @param target where the item was dragged to (can be null if the item was flung)
+     */
+    private void endDragging(View target, boolean isFlingToDelete, boolean success) {
+        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+                !(target instanceof DeleteDropTarget))) {
+            // Exit spring loaded mode if we have not successfully dropped or have not handled the
+            // drop in Workspace
+            mLauncher.exitSpringLoadedDragMode();
+        }
+        mLauncher.unlockScreenOrientation(false);
+    }
+
+    @Override
+    public View getContent() {
+        return null;
+    }
+
+    @Override
+    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+        mInTransition = true;
+        if (toWorkspace) {
+            cancelAllTasks();
+        }
+    }
+
+    @Override
+    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+    }
+
+    @Override
+    public void onLauncherTransitionStep(Launcher l, float t) {
+    }
+
+    @Override
+    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+        mInTransition = false;
+        for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
+            onSyncWidgetPageItems(d);
+        }
+        mDeferredSyncWidgetPageItems.clear();
+        for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
+            r.run();
+        }
+        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
+        mForceDrawAllChildrenNextFrame = !toWorkspace;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
+        if (isFlingToDelete) return;
+
+        endDragging(target, false, success);
+
+        // Display an error message if the drag failed due to there not being enough space on the
+        // target layout we were dropping on.
+        if (!success) {
+            boolean showOutOfSpaceMessage = false;
+            if (target instanceof Workspace) {
+                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
+                Workspace workspace = (Workspace) target;
+                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
+                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
+                if (layout != null) {
+                    layout.calculateSpans(itemInfo);
+                    showOutOfSpaceMessage =
+                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
+                }
+            }
+            if (showOutOfSpaceMessage) {
+                mLauncher.showOutOfSpaceMessage(false);
+            }
+
+            d.deferDragViewCleanupPostAnimation = false;
+        }
+        cleanupWidgetPreloading(success);
+        mDraggingWidget = false;
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // We just dismiss the drag when we fling, so cleanup here
+        endDragging(null, true, true);
+        cleanupWidgetPreloading(false);
+        mDraggingWidget = false;
+    }
+
+    @Override
+    public boolean supportsFlingToDelete() {
+        return true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        cancelAllTasks();
+    }
+
+    public void clearAllWidgetPages() {
+        cancelAllTasks();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View v = getPageAt(i);
+            if (v instanceof PagedViewGridLayout) {
+                ((PagedViewGridLayout) v).removeAllViewsOnPage();
+                mDirtyPageContent.set(i, true);
+            }
+        }
+    }
+
+    private void cancelAllTasks() {
+        // Clean up all the async tasks
+        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+        while (iter.hasNext()) {
+            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+            task.cancel(false);
+            iter.remove();
+            mDirtyPageContent.set(task.page, true);
+
+            // We've already preallocated the views for the data to load into, so clear them as well
+            View v = getPageAt(task.page);
+            if (v instanceof PagedViewGridLayout) {
+                ((PagedViewGridLayout) v).removeAllViewsOnPage();
+            }
+        }
+        mDeferredSyncWidgetPageItems.clear();
+        mDeferredPrepareLoadWidgetPreviewsTasks.clear();
+    }
+
+    public void setContentType(ContentType type) {
+        int page = getCurrentPage();
+        if (mContentType != type) {
+            page = 0;
+        }
+        mContentType = type;
+        invalidatePageData(page, true);
+    }
+
+    public ContentType getContentType() {
+        return mContentType;
+    }
+
+    protected void snapToPage(int whichPage, int delta, int duration) {
+        super.snapToPage(whichPage, delta, duration);
+
+        // Update the thread priorities given the direction lookahead
+        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+        while (iter.hasNext()) {
+            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+            int pageIndex = task.page;
+            if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
+                (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
+                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+            } else {
+                task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
+            }
+        }
+    }
+
+    /*
+     * Apps PagedView implementation
+     */
+    private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
+        int childCount = layout.getChildCount();
+        for (int i = 0; i < childCount; ++i) {
+            layout.getChildAt(i).setVisibility(visibility);
+        }
+    }
+    private void setupPage(AppsCustomizeCellLayout layout) {
+        layout.setGridSize(mCellCountX, mCellCountY);
+
+        // Note: We force a measure here to get around the fact that when we do layout calculations
+        // immediately after syncing, we don't have a proper width.  That said, we already know the
+        // expected page width, so we can actually optimize by hiding all the TextView-based
+        // children that are expensive to measure, and let that happen naturally later.
+        setVisibilityOnChildren(layout, View.GONE);
+        int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+        int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+        layout.setMinimumWidth(getPageContentWidth());
+        layout.measure(widthSpec, heightSpec);
+        setVisibilityOnChildren(layout, View.VISIBLE);
+    }
+
+    public void syncAppsPageItems(int page, boolean immediate) {
+        // ensure that we have the right number of items on the pages
+        final boolean isRtl = isLayoutRtl();
+        int numCells = mCellCountX * mCellCountY;
+        int startIndex = page * numCells;
+        int endIndex = Math.min(startIndex + numCells, mApps.size());
+        AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
+
+        layout.removeAllViewsOnPage();
+        ArrayList<Object> items = new ArrayList<Object>();
+        ArrayList<Bitmap> images = new ArrayList<Bitmap>();
+        for (int i = startIndex; i < endIndex; ++i) {
+            AppInfo info = mApps.get(i);
+            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
+                    R.layout.apps_customize_application, layout, false);
+            icon.applyFromApplicationInfo(info, true, this);
+            icon.setOnClickListener(this);
+            icon.setOnLongClickListener(this);
+            icon.setOnTouchListener(this);
+            icon.setOnKeyListener(this);
+
+            int index = i - startIndex;
+            int x = index % mCellCountX;
+            int y = index / mCellCountX;
+            if (isRtl) {
+                x = mCellCountX - x - 1;
+            }
+            layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
+
+            items.add(info);
+            images.add(info.iconBitmap);
+        }
+
+        enableHwLayersOnVisiblePages();
+    }
+
+    /**
+     * A helper to return the priority for loading of the specified widget page.
+     */
+    private int getWidgetPageLoadPriority(int page) {
+        // If we are snapping to another page, use that index as the target page index
+        int toPage = mCurrentPage;
+        if (mNextPage > -1) {
+            toPage = mNextPage;
+        }
+
+        // We use the distance from the target page as an initial guess of priority, but if there
+        // are no pages of higher priority than the page specified, then bump up the priority of
+        // the specified page.
+        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+        int minPageDiff = Integer.MAX_VALUE;
+        while (iter.hasNext()) {
+            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+            minPageDiff = Math.abs(task.page - toPage);
+        }
+
+        int rawPageDiff = Math.abs(page - toPage);
+        return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
+    }
+    /**
+     * Return the appropriate thread priority for loading for a given page (we give the current
+     * page much higher priority)
+     */
+    private int getThreadPriorityForPage(int page) {
+        // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
+        int pageDiff = getWidgetPageLoadPriority(page);
+        if (pageDiff <= 0) {
+            return Process.THREAD_PRIORITY_LESS_FAVORABLE;
+        } else if (pageDiff <= 1) {
+            return Process.THREAD_PRIORITY_LOWEST;
+        } else {
+            return Process.THREAD_PRIORITY_LOWEST;
+        }
+    }
+    private int getSleepForPage(int page) {
+        int pageDiff = getWidgetPageLoadPriority(page);
+        return Math.max(0, pageDiff * sPageSleepDelay);
+    }
+    /**
+     * Creates and executes a new AsyncTask to load a page of widget previews.
+     */
+    private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
+            int cellWidth, int cellHeight, int cellCountX) {
+
+        // Prune all tasks that are no longer needed
+        Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+        while (iter.hasNext()) {
+            AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+            int taskPage = task.page;
+            if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
+                    taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
+                task.cancel(false);
+                iter.remove();
+            } else {
+                task.setThreadPriority(getThreadPriorityForPage(taskPage));
+            }
+        }
+
+        // We introduce a slight delay to order the loading of side pages so that we don't thrash
+        final int sleepMs = getSleepForPage(page);
+        AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
+            new AsyncTaskCallback() {
+                @Override
+                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
+                    try {
+                        try {
+                            Thread.sleep(sleepMs);
+                        } catch (Exception e) {}
+                        loadWidgetPreviewsInBackground(task, data);
+                    } finally {
+                        if (task.isCancelled()) {
+                            data.cleanup(true);
+                        }
+                    }
+                }
+            },
+            new AsyncTaskCallback() {
+                @Override
+                public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
+                    mRunningTasks.remove(task);
+                    if (task.isCancelled()) return;
+                    // do cleanup inside onSyncWidgetPageItems
+                    onSyncWidgetPageItems(data);
+                }
+            }, mWidgetPreviewLoader);
+
+        // Ensure that the task is appropriately prioritized and runs in parallel
+        AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
+                AsyncTaskPageData.Type.LoadWidgetPreviewData);
+        t.setThreadPriority(getThreadPriorityForPage(page));
+        t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
+        mRunningTasks.add(t);
+    }
+
+    /*
+     * Widgets PagedView implementation
+     */
+    private void setupPage(PagedViewGridLayout layout) {
+        // Note: We force a measure here to get around the fact that when we do layout calculations
+        // immediately after syncing, we don't have a proper width.
+        int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+        int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+        layout.setMinimumWidth(getPageContentWidth());
+        layout.measure(widthSpec, heightSpec);
+    }
+
+    public void syncWidgetPageItems(final int page, final boolean immediate) {
+        int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+
+        // Calculate the dimensions of each cell we are giving to each widget
+        final ArrayList<Object> items = new ArrayList<Object>();
+        int contentWidth = mContentWidth;
+        final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
+                - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
+        int contentHeight = mContentHeight;
+        final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
+                - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
+
+        // Prepare the set of widgets to load previews for in the background
+        int offset = page * numItemsPerPage;
+        for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
+            items.add(mWidgets.get(i));
+        }
+
+        // Prepopulate the pages with the other widget info, and fill in the previews later
+        final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+        layout.setColumnCount(layout.getCellCountX());
+        for (int i = 0; i < items.size(); ++i) {
+            Object rawInfo = items.get(i);
+            PendingAddItemInfo createItemInfo = null;
+            PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
+                    R.layout.apps_customize_widget, layout, false);
+            if (rawInfo instanceof AppWidgetProviderInfo) {
+                // Fill in the widget information
+                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
+                createItemInfo = new PendingAddWidgetInfo(info, null, null);
+
+                // Determine the widget spans and min resize spans.
+                int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
+                createItemInfo.spanX = spanXY[0];
+                createItemInfo.spanY = spanXY[1];
+                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
+                createItemInfo.minSpanX = minSpanXY[0];
+                createItemInfo.minSpanY = minSpanXY[1];
+
+                widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader);
+                widget.setTag(createItemInfo);
+                widget.setShortPressListener(this);
+            } else if (rawInfo instanceof ResolveInfo) {
+                // Fill in the shortcuts information
+                ResolveInfo info = (ResolveInfo) rawInfo;
+                createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
+                createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+                createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
+                        info.activityInfo.name);
+                widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader);
+                widget.setTag(createItemInfo);
+            }
+            widget.setOnClickListener(this);
+            widget.setOnLongClickListener(this);
+            widget.setOnTouchListener(this);
+            widget.setOnKeyListener(this);
+
+            // Layout each widget
+            int ix = i % mWidgetCountX;
+            int iy = i / mWidgetCountX;
+            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
+                    GridLayout.spec(iy, GridLayout.START),
+                    GridLayout.spec(ix, GridLayout.TOP));
+            lp.width = cellWidth;
+            lp.height = cellHeight;
+            lp.setGravity(Gravity.TOP | Gravity.START);
+            if (ix > 0) lp.leftMargin = mWidgetWidthGap;
+            if (iy > 0) lp.topMargin = mWidgetHeightGap;
+            layout.addView(widget, lp);
+        }
+
+        // wait until a call on onLayout to start loading, because
+        // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
+        // TODO: can we do a measure/layout immediately?
+        layout.setOnLayoutListener(new Runnable() {
+            public void run() {
+                // Load the widget previews
+                int maxPreviewWidth = cellWidth;
+                int maxPreviewHeight = cellHeight;
+                if (layout.getChildCount() > 0) {
+                    PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
+                    int[] maxSize = w.getPreviewSize();
+                    maxPreviewWidth = maxSize[0];
+                    maxPreviewHeight = maxSize[1];
+                }
+
+                mWidgetPreviewLoader.setPreviewSize(
+                        maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
+                if (immediate) {
+                    AsyncTaskPageData data = new AsyncTaskPageData(page, items,
+                            maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
+                    loadWidgetPreviewsInBackground(null, data);
+                    onSyncWidgetPageItems(data);
+                } else {
+                    if (mInTransition) {
+                        mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
+                    } else {
+                        prepareLoadWidgetPreviewsTask(page, items,
+                                maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
+                    }
+                }
+                layout.setOnLayoutListener(null);
+            }
+        });
+    }
+    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
+            AsyncTaskPageData data) {
+        // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
+        // previews synchronously
+        if (task != null) {
+            // Ensure that this task starts running at the correct priority
+            task.syncThreadPriority();
+        }
+
+        // Load each of the widget/shortcut previews
+        ArrayList<Object> items = data.items;
+        ArrayList<Bitmap> images = data.generatedImages;
+        int count = items.size();
+        for (int i = 0; i < count; ++i) {
+            if (task != null) {
+                // Ensure we haven't been cancelled yet
+                if (task.isCancelled()) break;
+                // Before work on each item, ensure that this task is running at the correct
+                // priority
+                task.syncThreadPriority();
+            }
+
+            images.add(mWidgetPreviewLoader.getPreview(items.get(i)));
+        }
+    }
+
+    private void onSyncWidgetPageItems(AsyncTaskPageData data) {
+        if (mInTransition) {
+            mDeferredSyncWidgetPageItems.add(data);
+            return;
+        }
+        try {
+            int page = data.page;
+            PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+
+            ArrayList<Object> items = data.items;
+            int count = items.size();
+            for (int i = 0; i < count; ++i) {
+                PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
+                if (widget != null) {
+                    Bitmap preview = data.generatedImages.get(i);
+                    widget.applyPreview(new FastBitmapDrawable(preview), i);
+                }
+            }
+
+            enableHwLayersOnVisiblePages();
+
+            // Update all thread priorities
+            Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+            while (iter.hasNext()) {
+                AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+                int pageIndex = task.page;
+                task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+            }
+        } finally {
+            data.cleanup(false);
+        }
+    }
+
+    @Override
+    public void syncPages() {
+        disablePagedViewAnimations();
+
+        removeAllViews();
+        cancelAllTasks();
+
+        Context context = getContext();
+        if (mContentType == ContentType.Applications) {
+            for (int i = 0; i < mNumAppsPages; ++i) {
+                AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
+                setupPage(layout);
+                addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
+                        LayoutParams.MATCH_PARENT));
+            }
+        } else if (mContentType == ContentType.Widgets) {
+            for (int j = 0; j < mNumWidgetPages; ++j) {
+                PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
+                        mWidgetCountY);
+                setupPage(layout);
+                addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
+                        LayoutParams.MATCH_PARENT));
+            }
+        } else {
+            throw new RuntimeException("Invalid ContentType");
+        }
+
+        enablePagedViewAnimations();
+    }
+
+    @Override
+    public void syncPageItems(int page, boolean immediate) {
+        if (mContentType == ContentType.Widgets) {
+            syncWidgetPageItems(page, immediate);
+        } else {
+            syncAppsPageItems(page, immediate);
+        }
+    }
+
+    // We want our pages to be z-ordered such that the further a page is to the left, the higher
+    // it is in the z-order. This is important to insure touch events are handled correctly.
+    View getPageAt(int index) {
+        return getChildAt(indexToPage(index));
+    }
+
+    @Override
+    protected int indexToPage(int index) {
+        return getChildCount() - index - 1;
+    }
+
+    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
+    @Override
+    protected void screenScrolled(int screenCenter) {
+        final boolean isRtl = isLayoutRtl();
+        super.screenScrolled(screenCenter);
+
+        for (int i = 0; i < getChildCount(); i++) {
+            View v = getPageAt(i);
+            if (v != null) {
+                float scrollProgress = getScrollProgress(screenCenter, v, i);
+
+                float interpolatedProgress;
+                float translationX;
+                float maxScrollProgress = Math.max(0, scrollProgress);
+                float minScrollProgress = Math.min(0, scrollProgress);
+
+                if (isRtl) {
+                    translationX = maxScrollProgress * v.getMeasuredWidth();
+                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
+                } else {
+                    translationX = minScrollProgress * v.getMeasuredWidth();
+                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
+                }
+                float scale = (1 - interpolatedProgress) +
+                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
+
+                float alpha;
+                if (isRtl && (scrollProgress > 0)) {
+                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
+                } else if (!isRtl && (scrollProgress < 0)) {
+                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
+                } else {
+                    //  On large screens we need to fade the page as it nears its leftmost position
+                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
+                }
+
+                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
+                int pageWidth = v.getMeasuredWidth();
+                int pageHeight = v.getMeasuredHeight();
+
+                if (PERFORM_OVERSCROLL_ROTATION) {
+                    float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
+                    boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
+                    boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
+
+                    if (i == 0 && isOverscrollingFirstPage) {
+                        // Overscroll to the left
+                        v.setPivotX(xPivot * pageWidth);
+                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+                        scale = 1.0f;
+                        alpha = 1.0f;
+                        // On the first page, we don't want the page to have any lateral motion
+                        translationX = 0;
+                    } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
+                        // Overscroll to the right
+                        v.setPivotX((1 - xPivot) * pageWidth);
+                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+                        scale = 1.0f;
+                        alpha = 1.0f;
+                        // On the last page, we don't want the page to have any lateral motion.
+                        translationX = 0;
+                    } else {
+                        v.setPivotY(pageHeight / 2.0f);
+                        v.setPivotX(pageWidth / 2.0f);
+                        v.setRotationY(0f);
+                    }
+                }
+
+                v.setTranslationX(translationX);
+                v.setScaleX(scale);
+                v.setScaleY(scale);
+                v.setAlpha(alpha);
+
+                // If the view has 0 alpha, we set it to be invisible so as to prevent
+                // it from accepting touches
+                if (alpha == 0) {
+                    v.setVisibility(INVISIBLE);
+                } else if (v.getVisibility() != VISIBLE) {
+                    v.setVisibility(VISIBLE);
+                }
+            }
+        }
+
+        enableHwLayersOnVisiblePages();
+    }
+
+    private void enableHwLayersOnVisiblePages() {
+        final int screenCount = getChildCount();
+
+        getVisiblePages(mTempVisiblePagesRange);
+        int leftScreen = mTempVisiblePagesRange[0];
+        int rightScreen = mTempVisiblePagesRange[1];
+        int forceDrawScreen = -1;
+        if (leftScreen == rightScreen) {
+            // make sure we're caching at least two pages always
+            if (rightScreen < screenCount - 1) {
+                rightScreen++;
+                forceDrawScreen = rightScreen;
+            } else if (leftScreen > 0) {
+                leftScreen--;
+                forceDrawScreen = leftScreen;
+            }
+        } else {
+            forceDrawScreen = leftScreen + 1;
+        }
+
+        for (int i = 0; i < screenCount; i++) {
+            final View layout = (View) getPageAt(i);
+            if (!(leftScreen <= i && i <= rightScreen &&
+                    (i == forceDrawScreen || shouldDrawChild(layout)))) {
+                layout.setLayerType(LAYER_TYPE_NONE, null);
+            }
+        }
+
+        for (int i = 0; i < screenCount; i++) {
+            final View layout = (View) getPageAt(i);
+
+            if (leftScreen <= i && i <= rightScreen &&
+                    (i == forceDrawScreen || shouldDrawChild(layout))) {
+                if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
+                    layout.setLayerType(LAYER_TYPE_HARDWARE, null);
+                }
+            }
+        }
+    }
+
+    protected void overScroll(float amount) {
+        acceleratedOverScroll(amount);
+    }
+
+    /**
+     * Used by the parent to get the content width to set the tab bar to
+     * @return
+     */
+    public int getPageContentWidth() {
+        return mContentWidth;
+    }
+
+    @Override
+    protected void onPageEndMoving() {
+        super.onPageEndMoving();
+        mForceDrawAllChildrenNextFrame = true;
+        // We reset the save index when we change pages so that it will be recalculated on next
+        // rotation
+        mSaveInstanceStateItemIndex = -1;
+    }
+
+    /*
+     * AllAppsView implementation
+     */
+    public void setup(Launcher launcher, DragController dragController) {
+        mLauncher = launcher;
+        mDragController = dragController;
+    }
+
+    /**
+     * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
+     * appropriately determine when to invalidate the PagedView page data.  In cases where the data
+     * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
+     * next onMeasure() pass, which will trigger an invalidatePageData() itself.
+     */
+    private void invalidateOnDataChange() {
+        if (!isDataReady()) {
+            // The next layout pass will trigger data-ready if both widgets and apps are set, so
+            // request a layout to trigger the page data when ready.
+            requestLayout();
+        } else {
+            cancelAllTasks();
+            invalidatePageData();
+        }
+    }
+
+    public void setApps(ArrayList<AppInfo> list) {
+        if (!DISABLE_ALL_APPS) {
+            mApps = list;
+            Collections.sort(mApps, LauncherModel.getAppNameComparator());
+            updatePageCountsAndInvalidateData();
+        }
+    }
+    private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
+        // We add it in place, in alphabetical order
+        int count = list.size();
+        for (int i = 0; i < count; ++i) {
+            AppInfo info = list.get(i);
+            int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
+            if (index < 0) {
+                mApps.add(-(index + 1), info);
+            }
+        }
+    }
+    public void addApps(ArrayList<AppInfo> list) {
+        if (!DISABLE_ALL_APPS) {
+            addAppsWithoutInvalidate(list);
+            updatePageCountsAndInvalidateData();
+        }
+    }
+    private int findAppByComponent(List<AppInfo> list, AppInfo item) {
+        ComponentName removeComponent = item.intent.getComponent();
+        int length = list.size();
+        for (int i = 0; i < length; ++i) {
+            AppInfo info = list.get(i);
+            if (info.intent.getComponent().equals(removeComponent)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) {
+        // loop through all the apps and remove apps that have the same component
+        int length = list.size();
+        for (int i = 0; i < length; ++i) {
+            AppInfo info = list.get(i);
+            int removeIndex = findAppByComponent(mApps, info);
+            if (removeIndex > -1) {
+                mApps.remove(removeIndex);
+            }
+        }
+    }
+    public void removeApps(ArrayList<AppInfo> appInfos) {
+        if (!DISABLE_ALL_APPS) {
+            removeAppsWithoutInvalidate(appInfos);
+            updatePageCountsAndInvalidateData();
+        }
+    }
+    public void updateApps(ArrayList<AppInfo> list) {
+        // We remove and re-add the updated applications list because it's properties may have
+        // changed (ie. the title), and this will ensure that the items will be in their proper
+        // place in the list.
+        if (!DISABLE_ALL_APPS) {
+            removeAppsWithoutInvalidate(list);
+            addAppsWithoutInvalidate(list);
+            updatePageCountsAndInvalidateData();
+        }
+    }
+
+    public void reset() {
+        // If we have reset, then we should not continue to restore the previous state
+        mSaveInstanceStateItemIndex = -1;
+
+        AppsCustomizeTabHost tabHost = getTabHost();
+        String tag = tabHost.getCurrentTabTag();
+        if (tag != null) {
+            if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
+                tabHost.setCurrentTabFromContent(ContentType.Applications);
+            }
+        }
+
+        if (mCurrentPage != 0) {
+            invalidatePageData(0);
+        }
+    }
+
+    private AppsCustomizeTabHost getTabHost() {
+        return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
+    }
+
+    public void dumpState() {
+        // TODO: Dump information related to current list of Applications, Widgets, etc.
+        AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
+        dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
+    }
+
+    private void dumpAppWidgetProviderInfoList(String tag, String label,
+            ArrayList<Object> list) {
+        Log.d(tag, label + " size=" + list.size());
+        for (Object i: list) {
+            if (i instanceof AppWidgetProviderInfo) {
+                AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
+                Log.d(tag, "   label=\"" + info.label + "\" previewImage=" + info.previewImage
+                        + " resizeMode=" + info.resizeMode + " configure=" + info.configure
+                        + " initialLayout=" + info.initialLayout
+                        + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
+            } else if (i instanceof ResolveInfo) {
+                ResolveInfo info = (ResolveInfo) i;
+                Log.d(tag, "   label=\"" + info.loadLabel(mPackageManager) + "\" icon="
+                        + info.icon);
+            }
+        }
+    }
+
+    public void surrender() {
+        // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
+        // should stop this now.
+
+        // Stop all background tasks
+        cancelAllTasks();
+    }
+
+    @Override
+    public void iconPressed(PagedViewIcon icon) {
+        // Reset the previously pressed icon and store a reference to the pressed icon so that
+        // we can reset it on return to Launcher (in Launcher.onResume())
+        if (mPressedIcon != null) {
+            mPressedIcon.resetDrawableState();
+        }
+        mPressedIcon = icon;
+    }
+
+    public void resetDrawableState() {
+        if (mPressedIcon != null) {
+            mPressedIcon.resetDrawableState();
+            mPressedIcon = null;
+        }
+    }
+
+    /*
+     * We load an extra page on each side to prevent flashes from scrolling and loading of the
+     * widget previews in the background with the AsyncTasks.
+     */
+    final static int sLookBehindPageCount = 2;
+    final static int sLookAheadPageCount = 2;
+    protected int getAssociatedLowerPageBound(int page) {
+        final int count = getChildCount();
+        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
+        int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
+        return windowMinIndex;
+    }
+    protected int getAssociatedUpperPageBound(int page) {
+        final int count = getChildCount();
+        int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
+        int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
+                count - 1);
+        return windowMaxIndex;
+    }
+
+    protected String getCurrentPageDescription() {
+        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        int stringId = R.string.default_scroll_format;
+        int count = 0;
+
+        if (mContentType == ContentType.Applications) {
+            stringId = R.string.apps_customize_apps_scroll_format;
+            count = mNumAppsPages;
+        } else if (mContentType == ContentType.Widgets) {
+            stringId = R.string.apps_customize_widgets_scroll_format;
+            count = mNumWidgetPages;
+        } else {
+            throw new RuntimeException("Invalid ContentType");
+        }
+
+        return String.format(getContext().getString(stringId), page + 1, count);
+    }
+}
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
new file mode 100644
index 0000000..bfcf92a
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -0,0 +1,478 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable,
+        TabHost.OnTabChangeListener, Insettable  {
+    static final String LOG_TAG = "AppsCustomizeTabHost";
+
+    private static final String APPS_TAB_TAG = "APPS";
+    private static final String WIDGETS_TAB_TAG = "WIDGETS";
+
+    private final LayoutInflater mLayoutInflater;
+    private ViewGroup mTabs;
+    private ViewGroup mTabsContainer;
+    private AppsCustomizePagedView mAppsCustomizePane;
+    private FrameLayout mAnimationBuffer;
+    private LinearLayout mContent;
+
+    private boolean mInTransition;
+    private boolean mTransitioningToWorkspace;
+    private boolean mResetAfterTransition;
+    private Runnable mRelayoutAndMakeVisible;
+    private final Rect mInsets = new Rect();
+
+    public AppsCustomizeTabHost(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mLayoutInflater = LayoutInflater.from(context);
+        mRelayoutAndMakeVisible = new Runnable() {
+                public void run() {
+                    mTabs.requestLayout();
+                    mTabsContainer.setAlpha(1f);
+                }
+            };
+    }
+
+    /**
+     * Convenience methods to select specific tabs.  We want to set the content type immediately
+     * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view
+     * reflects the new content (but doesn't do the animation and logic associated with changing
+     * tabs manually).
+     */
+    void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) {
+        setOnTabChangedListener(null);
+        onTabChangedStart();
+        onTabChangedEnd(type);
+        setCurrentTabByTag(getTabTagForContentType(type));
+        setOnTabChangedListener(this);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        FrameLayout.LayoutParams flp = (LayoutParams) mContent.getLayoutParams();
+        flp.topMargin = insets.top;
+        flp.bottomMargin = insets.bottom;
+        flp.leftMargin = insets.left;
+        flp.rightMargin = insets.right;
+        mContent.setLayoutParams(flp);
+    }
+
+    /**
+     * Setup the tab host and create all necessary tabs.
+     */
+    @Override
+    protected void onFinishInflate() {
+        // Setup the tab host
+        setup();
+
+        final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container);
+        final TabWidget tabs = getTabWidget();
+        final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView)
+                findViewById(R.id.apps_customize_pane_content);
+        mTabs = tabs;
+        mTabsContainer = tabsContainer;
+        mAppsCustomizePane = appsCustomizePane;
+        mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer);
+        mContent = (LinearLayout) findViewById(R.id.apps_customize_content);
+        if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException();
+
+        // Configure the tabs content factory to return the same paged view (that we change the
+        // content filter on)
+        TabContentFactory contentFactory = new TabContentFactory() {
+            public View createTabContent(String tag) {
+                return appsCustomizePane;
+            }
+        };
+
+        // Create the tabs
+        TextView tabView;
+        String label;
+        label = getContext().getString(R.string.all_apps_button_label);
+        tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false);
+        tabView.setText(label);
+        tabView.setContentDescription(label);
+        addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
+        label = getContext().getString(R.string.widgets_tab_label);
+        tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false);
+        tabView.setText(label);
+        tabView.setContentDescription(label);
+        addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
+        setOnTabChangedListener(this);
+
+        // Setup the key listener to jump between the last tab view and the market icon
+        AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener();
+        View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1);
+        lastTab.setOnKeyListener(keyListener);
+        View shopButton = findViewById(R.id.market_button);
+        shopButton.setOnKeyListener(keyListener);
+
+        // Hide the tab bar until we measure
+        mTabsContainer.setAlpha(0f);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // Set the width of the tab list to the content width
+        if (remeasureTabWidth) {
+            int contentWidth = mAppsCustomizePane.getPageContentWidth();
+            if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) {
+                // Set the width and show the tab bar
+                mTabs.getLayoutParams().width = contentWidth;
+                mRelayoutAndMakeVisible.run();
+            }
+
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+     public boolean onInterceptTouchEvent(MotionEvent ev) {
+         // If we are mid transitioning to the workspace, then intercept touch events here so we
+         // can ignore them, otherwise we just let all apps handle the touch events.
+         if (mInTransition && mTransitioningToWorkspace) {
+             return true;
+         }
+         return super.onInterceptTouchEvent(ev);
+     };
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // Allow touch events to fall through to the workspace if we are transitioning there
+        if (mInTransition && mTransitioningToWorkspace) {
+            return super.onTouchEvent(event);
+        }
+
+        // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall
+        // through to the workspace and trigger showWorkspace()
+        if (event.getY() < mAppsCustomizePane.getBottom()) {
+            return true;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void onTabChangedStart() {
+    }
+
+    private void reloadCurrentPage() {
+        mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+        mAppsCustomizePane.requestFocus();
+    }
+
+    private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) {
+        int bgAlpha = (int) (255 * (getResources().getInteger(
+            R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f));
+        setBackgroundColor(Color.argb(bgAlpha, 0, 0, 0));
+        mAppsCustomizePane.setContentType(type);
+    }
+
+    @Override
+    public void onTabChanged(String tabId) {
+        final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId);
+
+        // Animate the changing of the tab content by fading pages in and out
+        final Resources res = getResources();
+        final int duration = res.getInteger(R.integer.config_tabTransitionDuration);
+
+        // We post a runnable here because there is a delay while the first page is loading and
+        // the feedback from having changed the tab almost feels better than having it stick
+        post(new Runnable() {
+            @Override
+            public void run() {
+                if (mAppsCustomizePane.getMeasuredWidth() <= 0 ||
+                        mAppsCustomizePane.getMeasuredHeight() <= 0) {
+                    reloadCurrentPage();
+                    return;
+                }
+
+                // Take the visible pages and re-parent them temporarily to mAnimatorBuffer
+                // and then cross fade to the new pages
+                int[] visiblePageRange = new int[2];
+                mAppsCustomizePane.getVisiblePages(visiblePageRange);
+                if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) {
+                    // If we can't get the visible page ranges, then just skip the animation
+                    reloadCurrentPage();
+                    return;
+                }
+                ArrayList<View> visiblePages = new ArrayList<View>();
+                for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) {
+                    visiblePages.add(mAppsCustomizePane.getPageAt(i));
+                }
+
+                // We want the pages to be rendered in exactly the same way as they were when
+                // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer
+                // to be exactly the same as mAppsCustomizePane, and below, set the left/top
+                // parameters to be correct for each of the pages
+                mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0);
+
+                // mAppsCustomizePane renders its children in reverse order, so
+                // add the pages to mAnimationBuffer in reverse order to match that behavior
+                for (int i = visiblePages.size() - 1; i >= 0; i--) {
+                    View child = visiblePages.get(i);
+                    if (child instanceof AppsCustomizeCellLayout) {
+                        ((AppsCustomizeCellLayout) child).resetChildrenOnKeyListeners();
+                    } else if (child instanceof PagedViewGridLayout) {
+                        ((PagedViewGridLayout) child).resetChildrenOnKeyListeners();
+                    }
+                    PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false);
+                    mAppsCustomizePane.removeView(child);
+                    PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true);
+                    mAnimationBuffer.setAlpha(1f);
+                    mAnimationBuffer.setVisibility(View.VISIBLE);
+                    LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(),
+                            child.getMeasuredHeight());
+                    p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0);
+                    mAnimationBuffer.addView(child, p);
+                }
+
+                // Toggle the new content
+                onTabChangedStart();
+                onTabChangedEnd(type);
+
+                // Animate the transition
+                ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f);
+                outAnim.addListener(new AnimatorListenerAdapter() {
+                    private void clearAnimationBuffer() {
+                        mAnimationBuffer.setVisibility(View.GONE);
+                        PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(false);
+                        mAnimationBuffer.removeAllViews();
+                        PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(true);
+                    }
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        clearAnimationBuffer();
+                    }
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        clearAnimationBuffer();
+                    }
+                });
+                ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f);
+                inAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        reloadCurrentPage();
+                    }
+                });
+
+                final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet();
+                animSet.playTogether(outAnim, inAnim);
+                animSet.setDuration(duration);
+                animSet.start();
+            }
+        });
+    }
+
+    public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) {
+        setOnTabChangedListener(null);
+        setCurrentTabByTag(getTabTagForContentType(type));
+        setOnTabChangedListener(this);
+    }
+
+    /**
+     * Returns the content type for the specified tab tag.
+     */
+    public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) {
+        if (tag.equals(APPS_TAB_TAG)) {
+            return AppsCustomizePagedView.ContentType.Applications;
+        } else if (tag.equals(WIDGETS_TAB_TAG)) {
+            return AppsCustomizePagedView.ContentType.Widgets;
+        }
+        return AppsCustomizePagedView.ContentType.Applications;
+    }
+
+    /**
+     * Returns the tab tag for a given content type.
+     */
+    public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) {
+        if (type == AppsCustomizePagedView.ContentType.Applications) {
+            return APPS_TAB_TAG;
+        } else if (type == AppsCustomizePagedView.ContentType.Widgets) {
+            return WIDGETS_TAB_TAG;
+        }
+        return APPS_TAB_TAG;
+    }
+
+    /**
+     * Disable focus on anything under this view in the hierarchy if we are not visible.
+     */
+    @Override
+    public int getDescendantFocusability() {
+        if (getVisibility() != View.VISIBLE) {
+            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+        }
+        return super.getDescendantFocusability();
+    }
+
+    void reset() {
+        if (mInTransition) {
+            // Defer to after the transition to reset
+            mResetAfterTransition = true;
+        } else {
+            // Reset immediately
+            mAppsCustomizePane.reset();
+        }
+    }
+
+    private void enableAndBuildHardwareLayer() {
+        // isHardwareAccelerated() checks if we're attached to a window and if that
+        // window is HW accelerated-- we were sometimes not attached to a window
+        // and buildLayer was throwing an IllegalStateException
+        if (isHardwareAccelerated()) {
+            // Turn on hardware layers for performance
+            setLayerType(LAYER_TYPE_HARDWARE, null);
+
+            // force building the layer, so you don't get a blip early in an animation
+            // when the layer is created layer
+            buildLayer();
+        }
+    }
+
+    @Override
+    public View getContent() {
+        return mContent;
+    }
+
+    /* LauncherTransitionable overrides */
+    @Override
+    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+        mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace);
+        mInTransition = true;
+        mTransitioningToWorkspace = toWorkspace;
+
+        if (toWorkspace) {
+            // Going from All Apps -> Workspace
+            setVisibilityOfSiblingsWithLowerZOrder(VISIBLE);
+        } else {
+            // Going from Workspace -> All Apps
+            mContent.setVisibility(VISIBLE);
+
+            // Make sure the current page is loaded (we start loading the side pages after the
+            // transition to prevent slowing down the animation)
+            mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
+        }
+
+        if (mResetAfterTransition) {
+            mAppsCustomizePane.reset();
+            mResetAfterTransition = false;
+        }
+    }
+
+    @Override
+    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+        if (animated) {
+            enableAndBuildHardwareLayer();
+        }
+
+        // Dismiss the workspace cling
+        l.dismissWorkspaceCling(null);
+    }
+
+    @Override
+    public void onLauncherTransitionStep(Launcher l, float t) {
+        // Do nothing
+    }
+
+    @Override
+    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+        mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace);
+        mInTransition = false;
+        if (animated) {
+            setLayerType(LAYER_TYPE_NONE, null);
+        }
+
+        if (!toWorkspace) {
+            // Show the all apps cling (if not already shown)
+            mAppsCustomizePane.showAllAppsCling();
+            // Make sure adjacent pages are loaded (we wait until after the transition to
+            // prevent slowing down the animation)
+            mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+
+            // Going from Workspace -> All Apps
+            // NOTE: We should do this at the end since we check visibility state in some of the
+            // cling initialization/dismiss code above.
+            setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE);
+        }
+    }
+
+    private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
+        ViewGroup parent = (ViewGroup) getParent();
+        if (parent == null) return;
+
+        View overviewPanel = ((Launcher) getContext()).getOverviewPanel();
+        final int count = parent.getChildCount();
+        if (!isChildrenDrawingOrderEnabled()) {
+            for (int i = 0; i < count; i++) {
+                final View child = parent.getChildAt(i);
+                if (child == this) {
+                    break;
+                } else {
+                    if (child.getVisibility() == GONE || child == overviewPanel) {
+                        continue;
+                    }
+                    child.setVisibility(visibility);
+                }
+            }
+        } else {
+            throw new RuntimeException("Failed; can't get z-order of views");
+        }
+    }
+
+    public void onWindowVisible() {
+        if (getVisibility() == VISIBLE) {
+            mContent.setVisibility(VISIBLE);
+            // We unload the widget previews when the UI is hidden, so need to reload pages
+            // Load the current page synchronously, and the neighboring pages asynchronously
+            mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
+            mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+        }
+    }
+
+    public void onTrimMemory() {
+        mContent.setVisibility(GONE);
+        // Clear the widget pages of all their subviews - this will trigger the widget previews
+        // to delete their bitmaps
+        mAppsCustomizePane.clearAllWidgetPages();
+    }
+
+    boolean isTransitioning() {
+        return mInTransition;
+    }
+}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
new file mode 100644
index 0000000..287bb50
--- /dev/null
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -0,0 +1,393 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+/**
+ * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
+ * because we want to make the bubble taller than the text and TextView's clip is
+ * too aggressive.
+ */
+public class BubbleTextView extends TextView {
+    static final float SHADOW_LARGE_RADIUS = 4.0f;
+    static final float SHADOW_SMALL_RADIUS = 1.75f;
+    static final float SHADOW_Y_OFFSET = 2.0f;
+    static final int SHADOW_LARGE_COLOUR = 0xDD000000;
+    static final int SHADOW_SMALL_COLOUR = 0xCC000000;
+    static final float PADDING_H = 8.0f;
+    static final float PADDING_V = 3.0f;
+
+    private int mPrevAlpha = -1;
+
+    private HolographicOutlineHelper mOutlineHelper;
+    private final Canvas mTempCanvas = new Canvas();
+    private final Rect mTempRect = new Rect();
+    private boolean mDidInvalidateForPressedState;
+    private Bitmap mPressedOrFocusedBackground;
+    private int mFocusedOutlineColor;
+    private int mFocusedGlowColor;
+    private int mPressedOutlineColor;
+    private int mPressedGlowColor;
+
+    private int mTextColor;
+    private boolean mShadowsEnabled = true;
+    private boolean mIsTextVisible;
+
+    private boolean mBackgroundSizeChanged;
+    private Drawable mBackground;
+
+    private boolean mStayPressed;
+    private CheckLongPressHelper mLongPressHelper;
+
+    public BubbleTextView(Context context) {
+        super(context);
+        init();
+    }
+
+    public BubbleTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    public void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Ensure we are using the right text size
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+        setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
+    }
+
+    private void init() {
+        mLongPressHelper = new CheckLongPressHelper(this);
+        mBackground = getBackground();
+
+        mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+
+        final Resources res = getContext().getResources();
+        mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor =
+            res.getColor(R.color.outline_color);
+
+        setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
+    }
+
+    public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
+        Bitmap b = info.getIcon(iconCache);
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        setCompoundDrawablesWithIntrinsicBounds(null,
+                new FastBitmapDrawable(b),
+                null, null);
+        setCompoundDrawablePadding((int) ((grid.folderIconSizePx - grid.iconSizePx) / 2f));
+        setText(info.title);
+        setTag(info);
+    }
+
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) {
+            mBackgroundSizeChanged = true;
+        }
+        return super.setFrame(left, top, right, bottom);
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mBackground || super.verifyDrawable(who);
+    }
+
+    @Override
+    public void setTag(Object tag) {
+        if (tag != null) {
+            LauncherModel.checkItemInfo((ItemInfo) tag);
+        }
+        super.setTag(tag);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        if (isPressed()) {
+            // In this case, we have already created the pressed outline on ACTION_DOWN,
+            // so we just need to do an invalidate to trigger draw
+            if (!mDidInvalidateForPressedState) {
+                setCellLayoutPressedOrFocusedIcon();
+            }
+        } else {
+            // Otherwise, either clear the pressed/focused background, or create a background
+            // for the focused state
+            final boolean backgroundEmptyBefore = mPressedOrFocusedBackground == null;
+            if (!mStayPressed) {
+                mPressedOrFocusedBackground = null;
+            }
+            if (isFocused()) {
+                if (getLayout() == null) {
+                    // In some cases, we get focus before we have been layed out. Set the
+                    // background to null so that it will get created when the view is drawn.
+                    mPressedOrFocusedBackground = null;
+                } else {
+                    mPressedOrFocusedBackground = createGlowingOutline(
+                            mTempCanvas, mFocusedGlowColor, mFocusedOutlineColor);
+                }
+                mStayPressed = false;
+                setCellLayoutPressedOrFocusedIcon();
+            }
+            final boolean backgroundEmptyNow = mPressedOrFocusedBackground == null;
+            if (!backgroundEmptyBefore && backgroundEmptyNow) {
+                setCellLayoutPressedOrFocusedIcon();
+            }
+        }
+
+        Drawable d = mBackground;
+        if (d != null && d.isStateful()) {
+            d.setState(getDrawableState());
+        }
+        super.drawableStateChanged();
+    }
+
+    /**
+     * Draw this BubbleTextView into the given Canvas.
+     *
+     * @param destCanvas the canvas to draw on
+     * @param padding the horizontal and vertical padding to use when drawing
+     */
+    private void drawWithPadding(Canvas destCanvas, int padding) {
+        final Rect clipRect = mTempRect;
+        getDrawingRect(clipRect);
+
+        // adjust the clip rect so that we don't include the text label
+        clipRect.bottom =
+            getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + getLayout().getLineTop(0);
+
+        // Draw the View into the bitmap.
+        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
+        // they set scrollX and scrollY to large values to achieve centered text
+        destCanvas.save();
+        destCanvas.scale(getScaleX(), getScaleY(),
+                (getWidth() + padding) / 2, (getHeight() + padding) / 2);
+        destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2);
+        destCanvas.clipRect(clipRect, Op.REPLACE);
+        draw(destCanvas);
+        destCanvas.restore();
+    }
+
+    /**
+     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createGlowingOutline(Canvas canvas, int outlineColor, int glowColor) {
+        final int padding = mOutlineHelper.mMaxOuterBlurRadius;
+        final Bitmap b = Bitmap.createBitmap(
+                getWidth() + padding, getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        drawWithPadding(canvas, padding);
+        mOutlineHelper.applyExtraThickExpensiveOutlineWithBlur(b, canvas, glowColor, outlineColor);
+        canvas.setBitmap(null);
+
+        return b;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // Call the superclass onTouchEvent first, because sometimes it changes the state to
+        // isPressed() on an ACTION_UP
+        boolean result = super.onTouchEvent(event);
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // So that the pressed outline is visible immediately when isPressed() is true,
+                // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
+                // to create it)
+                if (mPressedOrFocusedBackground == null) {
+                    mPressedOrFocusedBackground = createGlowingOutline(
+                            mTempCanvas, mPressedGlowColor, mPressedOutlineColor);
+                }
+                // Invalidate so the pressed state is visible, or set a flag so we know that we
+                // have to call invalidate as soon as the state is "pressed"
+                if (isPressed()) {
+                    mDidInvalidateForPressedState = true;
+                    setCellLayoutPressedOrFocusedIcon();
+                } else {
+                    mDidInvalidateForPressedState = false;
+                }
+
+                mLongPressHelper.postCheckForLongPress();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                // If we've touched down and up on an item, and it's still not "pressed", then
+                // destroy the pressed outline
+                if (!isPressed()) {
+                    mPressedOrFocusedBackground = null;
+                }
+
+                mLongPressHelper.cancelLongPress();
+                break;
+        }
+        return result;
+    }
+
+    void setStayPressed(boolean stayPressed) {
+        mStayPressed = stayPressed;
+        if (!stayPressed) {
+            mPressedOrFocusedBackground = null;
+        }
+        setCellLayoutPressedOrFocusedIcon();
+    }
+
+    void setCellLayoutPressedOrFocusedIcon() {
+        if (getParent() instanceof ShortcutAndWidgetContainer) {
+            ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent();
+            if (parent != null) {
+                CellLayout layout = (CellLayout) parent.getParent();
+                layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
+            }
+        }
+    }
+
+    void clearPressedOrFocusedBackground() {
+        mPressedOrFocusedBackground = null;
+        setCellLayoutPressedOrFocusedIcon();
+    }
+
+    Bitmap getPressedOrFocusedBackground() {
+        return mPressedOrFocusedBackground;
+    }
+
+    int getPressedOrFocusedBackgroundPadding() {
+        return mOutlineHelper.mMaxOuterBlurRadius / 2;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (!mShadowsEnabled) {
+            super.draw(canvas);
+            return;
+        }
+
+        final Drawable background = mBackground;
+        if (background != null) {
+            final int scrollX = getScrollX();
+            final int scrollY = getScrollY();
+
+            if (mBackgroundSizeChanged) {
+                background.setBounds(0, 0,  getRight() - getLeft(), getBottom() - getTop());
+                mBackgroundSizeChanged = false;
+            }
+
+            if ((scrollX | scrollY) == 0) {
+                background.draw(canvas);
+            } else {
+                canvas.translate(scrollX, scrollY);
+                background.draw(canvas);
+                canvas.translate(-scrollX, -scrollY);
+            }
+        }
+
+        // If text is transparent, don't draw any shadow
+        if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+            getPaint().clearShadowLayer();
+            super.draw(canvas);
+            return;
+        }
+
+        // We enhance the shadow by drawing the shadow twice
+        getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
+        super.draw(canvas);
+        canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+                getScrollX() + getWidth(),
+                getScrollY() + getHeight(), Region.Op.INTERSECT);
+        getPaint().setShadowLayer(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR);
+        super.draw(canvas);
+        canvas.restore();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mBackground != null) mBackground.setCallback(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mBackground != null) mBackground.setCallback(null);
+    }
+
+    @Override
+    public void setTextColor(int color) {
+        mTextColor = color;
+        super.setTextColor(color);
+    }
+
+    public void setShadowsEnabled(boolean enabled) {
+        mShadowsEnabled = enabled;
+        getPaint().clearShadowLayer();
+        invalidate();
+    }
+
+    public void setTextVisibility(boolean visible) {
+        Resources res = getResources();
+        if (visible) {
+            super.setTextColor(mTextColor);
+        } else {
+            super.setTextColor(res.getColor(android.R.color.transparent));
+        }
+        mIsTextVisible = visible;
+    }
+
+    public boolean isTextVisible() {
+        return mIsTextVisible;
+    }
+
+    @Override
+    protected boolean onSetAlpha(int alpha) {
+        if (mPrevAlpha != alpha) {
+            mPrevAlpha = alpha;
+            super.onSetAlpha(alpha);
+        }
+        return true;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        mLongPressHelper.cancelLongPress();
+    }
+}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
new file mode 100644
index 0000000..d51ae46
--- /dev/null
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -0,0 +1,165 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+
+/**
+ * Implements a DropTarget.
+ */
+public class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener {
+
+    protected final int mTransitionDuration;
+
+    protected Launcher mLauncher;
+    private int mBottomDragPadding;
+    protected TextView mText;
+    protected SearchDropTargetBar mSearchDropTargetBar;
+
+    /** Whether this drop target is active for the current drag */
+    protected boolean mActive;
+
+    /** The paint applied to the drag view on hover */
+    protected int mHoverColor = 0;
+
+    public ButtonDropTarget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        Resources r = getResources();
+        mTransitionDuration = r.getInteger(R.integer.config_dropTargetBgTransitionDuration);
+        mBottomDragPadding = r.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
+    }
+
+    void setLauncher(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    public boolean acceptDrop(DragObject d) {
+        return false;
+    }
+
+    public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
+        mSearchDropTargetBar = searchDropTargetBar;
+    }
+
+    protected Drawable getCurrentDrawable() {
+        Drawable[] drawables = getCompoundDrawablesRelative();
+        for (int i = 0; i < drawables.length; ++i) {
+            if (drawables[i] != null) {
+                return drawables[i];
+            }
+        }
+        return null;
+    }
+
+    public void onDrop(DragObject d) {
+    }
+
+    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+        // Do nothing
+    }
+
+    public void onDragEnter(DragObject d) {
+        d.dragView.setColor(mHoverColor);
+    }
+
+    public void onDragOver(DragObject d) {
+        // Do nothing
+    }
+
+    public void onDragExit(DragObject d) {
+        d.dragView.setColor(0);
+    }
+
+    public void onDragStart(DragSource source, Object info, int dragAction) {
+        // Do nothing
+    }
+
+    public boolean isDropEnabled() {
+        return mActive;
+    }
+
+    public void onDragEnd() {
+        // Do nothing
+    }
+
+    @Override
+    public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
+        super.getHitRect(outRect);
+        outRect.bottom += mBottomDragPadding;
+
+        int[] coords = new int[2];
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
+        outRect.offsetTo(coords[0], coords[1]);
+    }
+
+    private boolean isRtl() {
+        return (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+    }
+
+    Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+        DragLayer dragLayer = mLauncher.getDragLayer();
+
+        // Find the rect to animate to (the view is center aligned)
+        Rect to = new Rect();
+        dragLayer.getViewRectRelativeToSelf(this, to);
+
+        final int width = drawableWidth;
+        final int height = drawableHeight;
+
+        final int left;
+        final int right;
+
+        if (isRtl()) {
+            right = to.right - getPaddingRight();
+            left = right - width;
+        } else {
+            left = to.left + getPaddingLeft();
+            right = left + width;
+        }
+
+        final int top = to.top + (getMeasuredHeight() - height) / 2;
+        final int bottom = top +  height;
+
+        to.set(left, top, right, bottom);
+
+        // Center the destination rect about the trash icon
+        final int xOffset = (int) -(viewWidth - width) / 2;
+        final int yOffset = (int) -(viewHeight - height) / 2;
+        to.offset(xOffset, yOffset);
+
+        return to;
+    }
+
+    public void getLocationInDragLayer(int[] loc) {
+        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+    }
+}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
new file mode 100644
index 0000000..dafb79f
--- /dev/null
+++ b/src/com/android/launcher3/CellLayout.java
@@ -0,0 +1,3321 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LayoutAnimationController;
+
+import com.android.launcher3.R;
+import com.android.launcher3.FolderIcon.FolderRingAnimator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Stack;
+
+public class CellLayout extends ViewGroup {
+    static final String TAG = "CellLayout";
+
+    private Launcher mLauncher;
+    private int mCellWidth;
+    private int mCellHeight;
+    private int mFixedCellWidth;
+    private int mFixedCellHeight;
+
+    private int mCountX;
+    private int mCountY;
+
+    private int mOriginalWidthGap;
+    private int mOriginalHeightGap;
+    private int mWidthGap;
+    private int mHeightGap;
+    private int mMaxGap;
+    private boolean mScrollingTransformsDirty = false;
+
+    private final Rect mRect = new Rect();
+    private final CellInfo mCellInfo = new CellInfo();
+
+    // These are temporary variables to prevent having to allocate a new object just to
+    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+    private final int[] mTmpXY = new int[2];
+    private final int[] mTmpPoint = new int[2];
+    int[] mTempLocation = new int[2];
+
+    boolean[][] mOccupied;
+    boolean[][] mTmpOccupied;
+    private boolean mLastDownOnOccupiedCell = false;
+
+    private OnTouchListener mInterceptTouchListener;
+
+    private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
+    private int[] mFolderLeaveBehindCell = {-1, -1};
+
+    private float FOREGROUND_ALPHA_DAMPER = 0.65f;
+    private int mForegroundAlpha = 0;
+    private float mBackgroundAlpha;
+    private float mBackgroundAlphaMultiplier = 1.0f;
+
+    private Drawable mNormalBackground;
+    private Drawable mActiveGlowBackground;
+    private Drawable mOverScrollForegroundDrawable;
+    private Drawable mOverScrollLeft;
+    private Drawable mOverScrollRight;
+    private Rect mBackgroundRect;
+    private Rect mForegroundRect;
+    private int mForegroundPadding;
+
+    // These values allow a fixed measurement to be set on the CellLayout.
+    private int mFixedWidth = -1;
+    private int mFixedHeight = -1;
+
+    // If we're actively dragging something over this screen, mIsDragOverlapping is true
+    private boolean mIsDragOverlapping = false;
+    boolean mUseActiveGlowBackground = false;
+
+    // These arrays are used to implement the drag visualization on x-large screens.
+    // They are used as circular arrays, indexed by mDragOutlineCurrent.
+    private Rect[] mDragOutlines = new Rect[4];
+    private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
+    private InterruptibleInOutAnimator[] mDragOutlineAnims =
+            new InterruptibleInOutAnimator[mDragOutlines.length];
+
+    // Used as an index into the above 3 arrays; indicates which is the most current value.
+    private int mDragOutlineCurrent = 0;
+    private final Paint mDragOutlinePaint = new Paint();
+
+    private BubbleTextView mPressedOrFocusedIcon;
+
+    private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
+            HashMap<CellLayout.LayoutParams, Animator>();
+    private HashMap<View, ReorderHintAnimation>
+            mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
+
+    private boolean mItemPlacementDirty = false;
+
+    // When a drag operation is in progress, holds the nearest cell to the touch point
+    private final int[] mDragCell = new int[2];
+
+    private boolean mDragging = false;
+
+    private TimeInterpolator mEaseOutInterpolator;
+    private ShortcutAndWidgetContainer mShortcutsAndWidgets;
+
+    private boolean mIsHotseat = false;
+    private float mHotseatScale = 1f;
+
+    public static final int MODE_DRAG_OVER = 0;
+    public static final int MODE_ON_DROP = 1;
+    public static final int MODE_ON_DROP_EXTERNAL = 2;
+    public static final int MODE_ACCEPT_DROP = 3;
+    private static final boolean DESTRUCTIVE_REORDER = false;
+    private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
+
+    static final int LANDSCAPE = 0;
+    static final int PORTRAIT = 1;
+
+    private static final float REORDER_HINT_MAGNITUDE = 0.12f;
+    private static final int REORDER_ANIMATION_DURATION = 150;
+    private float mReorderHintAnimationMagnitude;
+
+    private ArrayList<View> mIntersectingViews = new ArrayList<View>();
+    private Rect mOccupiedRect = new Rect();
+    private int[] mDirectionVector = new int[2];
+    int[] mPreviousReorderDirection = new int[2];
+    private static final int INVALID_DIRECTION = -100;
+    private DropTarget.DragEnforcer mDragEnforcer;
+
+    private Rect mTempRect = new Rect();
+
+    private final static PorterDuffXfermode sAddBlendMode =
+            new PorterDuffXfermode(PorterDuff.Mode.ADD);
+    private final static Paint sPaint = new Paint();
+
+    public CellLayout(Context context) {
+        this(context, null);
+    }
+
+    public CellLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CellLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mDragEnforcer = new DropTarget.DragEnforcer(context);
+
+        // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
+        // the user where a dragged item will land when dropped.
+        setWillNotDraw(false);
+        setClipToPadding(false);
+        mLauncher = (Launcher) context;
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+
+        mCellWidth = mCellHeight = -1;
+        mFixedCellHeight = mFixedCellHeight = -1;
+        mWidthGap = mOriginalWidthGap = 0;
+        mHeightGap = mOriginalHeightGap = 0;
+        mMaxGap = Integer.MAX_VALUE;
+        mCountX = (int) grid.numColumns;
+        mCountY = (int) grid.numRows;
+        mOccupied = new boolean[mCountX][mCountY];
+        mTmpOccupied = new boolean[mCountX][mCountY];
+        mPreviousReorderDirection[0] = INVALID_DIRECTION;
+        mPreviousReorderDirection[1] = INVALID_DIRECTION;
+
+        a.recycle();
+
+        setAlwaysDrawnWithCacheEnabled(false);
+
+        final Resources res = getResources();
+        mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
+
+        mNormalBackground = res.getDrawable(R.drawable.screenpanel);
+        mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
+
+        mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
+        mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
+        mForegroundPadding =
+                res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
+
+        mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
+                grid.iconSizePx);
+
+        mNormalBackground.setFilterBitmap(true);
+        mActiveGlowBackground.setFilterBitmap(true);
+
+        // Initialize the data structures used for the drag visualization.
+        mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
+        mDragCell[0] = mDragCell[1] = -1;
+        for (int i = 0; i < mDragOutlines.length; i++) {
+            mDragOutlines[i] = new Rect(-1, -1, -1, -1);
+        }
+
+        // When dragging things around the home screens, we show a green outline of
+        // where the item will land. The outlines gradually fade out, leaving a trail
+        // behind the drag path.
+        // Set up all the animations that are used to implement this fading.
+        final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
+        final float fromAlphaValue = 0;
+        final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
+
+        Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
+
+        for (int i = 0; i < mDragOutlineAnims.length; i++) {
+            final InterruptibleInOutAnimator anim =
+                new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
+            anim.getAnimator().setInterpolator(mEaseOutInterpolator);
+            final int thisIndex = i;
+            anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final Bitmap outline = (Bitmap)anim.getTag();
+
+                    // If an animation is started and then stopped very quickly, we can still
+                    // get spurious updates we've cleared the tag. Guard against this.
+                    if (outline == null) {
+                        @SuppressWarnings("all") // suppress dead code warning
+                        final boolean debug = false;
+                        if (debug) {
+                            Object val = animation.getAnimatedValue();
+                            Log.d(TAG, "anim " + thisIndex + " update: " + val +
+                                     ", isStopped " + anim.isStopped());
+                        }
+                        // Try to prevent it from continuing to run
+                        animation.cancel();
+                    } else {
+                        mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
+                        CellLayout.this.invalidate(mDragOutlines[thisIndex]);
+                    }
+                }
+            });
+            // The animation holds a reference to the drag outline bitmap as long is it's
+            // running. This way the bitmap can be GCed when the animations are complete.
+            anim.getAnimator().addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
+                        anim.setTag(null);
+                    }
+                }
+            });
+            mDragOutlineAnims[i] = anim;
+        }
+
+        mBackgroundRect = new Rect();
+        mForegroundRect = new Rect();
+
+        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+                mCountX, mCountY);
+
+        addView(mShortcutsAndWidgets);
+    }
+
+    public void enableHardwareLayer(boolean hasLayer) {
+        mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
+    }
+
+    public void buildHardwareLayer() {
+        mShortcutsAndWidgets.buildLayer();
+    }
+
+    public float getChildrenScale() {
+        return mIsHotseat ? mHotseatScale : 1.0f;
+    }
+
+    public void setCellDimensions(int width, int height) {
+        mFixedCellWidth = mCellWidth = width;
+        mFixedCellHeight = mCellHeight = height;
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+                mCountX, mCountY);
+    }
+
+    public void setGridSize(int x, int y) {
+        mCountX = x;
+        mCountY = y;
+        mOccupied = new boolean[mCountX][mCountY];
+        mTmpOccupied = new boolean[mCountX][mCountY];
+        mTempRectStack.clear();
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+                mCountX, mCountY);
+        requestLayout();
+    }
+
+    // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
+    public void setInvertIfRtl(boolean invert) {
+        mShortcutsAndWidgets.setInvertIfRtl(invert);
+    }
+
+    private void invalidateBubbleTextView(BubbleTextView icon) {
+        final int padding = icon.getPressedOrFocusedBackgroundPadding();
+        invalidate(icon.getLeft() + getPaddingLeft() - padding,
+                icon.getTop() + getPaddingTop() - padding,
+                icon.getRight() + getPaddingLeft() + padding,
+                icon.getBottom() + getPaddingTop() + padding);
+    }
+
+    void setOverScrollAmount(float r, boolean left) {
+        if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
+            mOverScrollForegroundDrawable = mOverScrollLeft;
+        } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
+            mOverScrollForegroundDrawable = mOverScrollRight;
+        }
+
+        r *= FOREGROUND_ALPHA_DAMPER;
+        mForegroundAlpha = (int) Math.round((r * 255));
+        mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
+        invalidate();
+    }
+
+    void setPressedOrFocusedIcon(BubbleTextView icon) {
+        // We draw the pressed or focused BubbleTextView's background in CellLayout because it
+        // requires an expanded clip rect (due to the glow's blur radius)
+        BubbleTextView oldIcon = mPressedOrFocusedIcon;
+        mPressedOrFocusedIcon = icon;
+        if (oldIcon != null) {
+            invalidateBubbleTextView(oldIcon);
+        }
+        if (mPressedOrFocusedIcon != null) {
+            invalidateBubbleTextView(mPressedOrFocusedIcon);
+        }
+    }
+
+    void setIsDragOverlapping(boolean isDragOverlapping) {
+        if (mIsDragOverlapping != isDragOverlapping) {
+            mIsDragOverlapping = isDragOverlapping;
+            setUseActiveGlowBackground(mIsDragOverlapping);
+            invalidate();
+        }
+    }
+
+    void setUseActiveGlowBackground(boolean use) {
+        mUseActiveGlowBackground = use;
+    }
+
+    boolean getIsDragOverlapping() {
+        return mIsDragOverlapping;
+    }
+
+    protected void setOverscrollTransformsDirty(boolean dirty) {
+        mScrollingTransformsDirty = dirty;
+    }
+
+    protected void resetOverscrollTransforms() {
+        if (mScrollingTransformsDirty) {
+            setOverscrollTransformsDirty(false);
+            setTranslationX(0);
+            setRotationY(0);
+            // It doesn't matter if we pass true or false here, the important thing is that we
+            // pass 0, which results in the overscroll drawable not being drawn any more.
+            setOverScrollAmount(0, false);
+            setPivotX(getMeasuredWidth() / 2);
+            setPivotY(getMeasuredHeight() / 2);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
+        // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
+        // When we're small, we are either drawn normally or in the "accepts drops" state (during
+        // a drag). However, we also drag the mini hover background *over* one of those two
+        // backgrounds
+        if (mBackgroundAlpha > 0.0f) {
+            Drawable bg;
+
+            if (mUseActiveGlowBackground) {
+                // In the mini case, we draw the active_glow bg *over* the active background
+                bg = mActiveGlowBackground;
+            } else {
+                bg = mNormalBackground;
+            }
+
+            bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
+            bg.setBounds(mBackgroundRect);
+            bg.draw(canvas);
+        }
+
+        final Paint paint = mDragOutlinePaint;
+        for (int i = 0; i < mDragOutlines.length; i++) {
+            final float alpha = mDragOutlineAlphas[i];
+            if (alpha > 0) {
+                final Rect r = mDragOutlines[i];
+                mTempRect.set(r);
+                Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
+                final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
+                paint.setAlpha((int)(alpha + .5f));
+                canvas.drawBitmap(b, null, mTempRect, paint);
+            }
+        }
+
+        // We draw the pressed or focused BubbleTextView's background in CellLayout because it
+        // requires an expanded clip rect (due to the glow's blur radius)
+        if (mPressedOrFocusedIcon != null) {
+            final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
+            final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
+            if (b != null) {
+                int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
+                        (mCountX * mCellWidth);
+                int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+                int top = getPaddingTop();
+                canvas.drawBitmap(b,
+                        mPressedOrFocusedIcon.getLeft() + left - padding,
+                        mPressedOrFocusedIcon.getTop() + top - padding,
+                        null);
+            }
+        }
+
+        if (DEBUG_VISUALIZE_OCCUPIED) {
+            int[] pt = new int[2];
+            ColorDrawable cd = new ColorDrawable(Color.RED);
+            cd.setBounds(0, 0,  mCellWidth, mCellHeight);
+            for (int i = 0; i < mCountX; i++) {
+                for (int j = 0; j < mCountY; j++) {
+                    if (mOccupied[i][j]) {
+                        cellToPoint(i, j, pt);
+                        canvas.save();
+                        canvas.translate(pt[0], pt[1]);
+                        cd.draw(canvas);
+                        canvas.restore();
+                    }
+                }
+            }
+        }
+
+        int previewOffset = FolderRingAnimator.sPreviewSize;
+
+        // The folder outer / inner ring image(s)
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        for (int i = 0; i < mFolderOuterRings.size(); i++) {
+            FolderRingAnimator fra = mFolderOuterRings.get(i);
+
+            Drawable d;
+            int width, height;
+            cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
+            View child = getChildAt(fra.mCellX, fra.mCellY);
+
+            if (child != null) {
+                int centerX = mTempLocation[0] + mCellWidth / 2;
+                int centerY = mTempLocation[1] + previewOffset / 2 +
+                        child.getPaddingTop() + grid.folderBackgroundOffset;
+
+                // Draw outer ring, if it exists
+                if (FolderIcon.HAS_OUTER_RING) {
+                    d = FolderRingAnimator.sSharedOuterRingDrawable;
+                    width = (int) (fra.getOuterRingSize() * getChildrenScale());
+                    height = width;
+                    canvas.save();
+                    canvas.translate(centerX - width / 2, centerY - height / 2);
+                    d.setBounds(0, 0, width, height);
+                    d.draw(canvas);
+                    canvas.restore();
+                }
+
+                // Draw inner ring
+                d = FolderRingAnimator.sSharedInnerRingDrawable;
+                width = (int) (fra.getInnerRingSize() * getChildrenScale());
+                height = width;
+                canvas.save();
+                canvas.translate(centerX - width / 2, centerY - width / 2);
+                d.setBounds(0, 0, width, height);
+                d.draw(canvas);
+                canvas.restore();
+            }
+        }
+
+        if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
+            Drawable d = FolderIcon.sSharedFolderLeaveBehind;
+            int width = d.getIntrinsicWidth();
+            int height = d.getIntrinsicHeight();
+
+            cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
+            View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
+            if (child != null) {
+                int centerX = mTempLocation[0] + mCellWidth / 2;
+                int centerY = mTempLocation[1] + previewOffset / 2 +
+                        child.getPaddingTop() + grid.folderBackgroundOffset;
+
+                canvas.save();
+                canvas.translate(centerX - width / 2, centerY - width / 2);
+                d.setBounds(0, 0, width, height);
+                d.draw(canvas);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mForegroundAlpha > 0) {
+            mOverScrollForegroundDrawable.setBounds(mForegroundRect);
+            mOverScrollForegroundDrawable.draw(canvas);
+        }
+    }
+
+    public void showFolderAccept(FolderRingAnimator fra) {
+        mFolderOuterRings.add(fra);
+    }
+
+    public void hideFolderAccept(FolderRingAnimator fra) {
+        if (mFolderOuterRings.contains(fra)) {
+            mFolderOuterRings.remove(fra);
+        }
+        invalidate();
+    }
+
+    public void setFolderLeaveBehindCell(int x, int y) {
+        mFolderLeaveBehindCell[0] = x;
+        mFolderLeaveBehindCell[1] = y;
+        invalidate();
+    }
+
+    public void clearFolderLeaveBehind() {
+        mFolderLeaveBehindCell[0] = -1;
+        mFolderLeaveBehindCell[1] = -1;
+        invalidate();
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    public void restoreInstanceState(SparseArray<Parcelable> states) {
+        dispatchRestoreInstanceState(states);
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
+    public void setOnInterceptTouchListener(View.OnTouchListener listener) {
+        mInterceptTouchListener = listener;
+    }
+
+    int getCountX() {
+        return mCountX;
+    }
+
+    int getCountY() {
+        return mCountY;
+    }
+
+    public void setIsHotseat(boolean isHotseat) {
+        mIsHotseat = isHotseat;
+        mShortcutsAndWidgets.setIsHotseat(isHotseat);
+    }
+
+    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
+            boolean markCells) {
+        final LayoutParams lp = params;
+
+        // Hotseat icons - remove text
+        if (child instanceof BubbleTextView) {
+            BubbleTextView bubbleChild = (BubbleTextView) child;
+            bubbleChild.setTextVisibility(!mIsHotseat);
+        }
+
+        child.setScaleX(getChildrenScale());
+        child.setScaleY(getChildrenScale());
+
+        // Generate an id for each view, this assumes we have at most 256x256 cells
+        // per workspace screen
+        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
+            // If the horizontal or vertical span is set to -1, it is taken to
+            // mean that it spans the extent of the CellLayout
+            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
+            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
+
+            child.setId(childId);
+
+            mShortcutsAndWidgets.addView(child, index, lp);
+
+            if (markCells) markCellsAsOccupiedForView(child);
+
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeAllViews() {
+        clearOccupiedCells();
+        mShortcutsAndWidgets.removeAllViews();
+    }
+
+    @Override
+    public void removeAllViewsInLayout() {
+        if (mShortcutsAndWidgets.getChildCount() > 0) {
+            clearOccupiedCells();
+            mShortcutsAndWidgets.removeAllViewsInLayout();
+        }
+    }
+
+    public void removeViewWithoutMarkingCells(View view) {
+        mShortcutsAndWidgets.removeView(view);
+    }
+
+    @Override
+    public void removeView(View view) {
+        markCellsAsUnoccupiedForView(view);
+        mShortcutsAndWidgets.removeView(view);
+    }
+
+    @Override
+    public void removeViewAt(int index) {
+        markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
+        mShortcutsAndWidgets.removeViewAt(index);
+    }
+
+    @Override
+    public void removeViewInLayout(View view) {
+        markCellsAsUnoccupiedForView(view);
+        mShortcutsAndWidgets.removeViewInLayout(view);
+    }
+
+    @Override
+    public void removeViews(int start, int count) {
+        for (int i = start; i < start + count; i++) {
+            markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
+        }
+        mShortcutsAndWidgets.removeViews(start, count);
+    }
+
+    @Override
+    public void removeViewsInLayout(int start, int count) {
+        for (int i = start; i < start + count; i++) {
+            markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
+        }
+        mShortcutsAndWidgets.removeViewsInLayout(start, count);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (getParent() instanceof Workspace) {
+            Workspace workspace = (Workspace) getParent();
+            mCellInfo.screenId = workspace.getIdForScreen(this);
+        }
+    }
+
+    public void setTagToCellInfoForPoint(int touchX, int touchY) {
+        final CellInfo cellInfo = mCellInfo;
+        Rect frame = mRect;
+        final int x = touchX + getScrollX();
+        final int y = touchY + getScrollY();
+        final int count = mShortcutsAndWidgets.getChildCount();
+
+        boolean found = false;
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = mShortcutsAndWidgets.getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
+                    lp.isLockedToGrid) {
+                child.getHitRect(frame);
+
+                float scale = child.getScaleX();
+                frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
+                        child.getBottom());
+                // The child hit rect is relative to the CellLayoutChildren parent, so we need to
+                // offset that by this CellLayout's padding to test an (x,y) point that is relative
+                // to this view.
+                frame.offset(getPaddingLeft(), getPaddingTop());
+                frame.inset((int) (frame.width() * (1f - scale) / 2),
+                        (int) (frame.height() * (1f - scale) / 2));
+
+                if (frame.contains(x, y)) {
+                    cellInfo.cell = child;
+                    cellInfo.cellX = lp.cellX;
+                    cellInfo.cellY = lp.cellY;
+                    cellInfo.spanX = lp.cellHSpan;
+                    cellInfo.spanY = lp.cellVSpan;
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        mLastDownOnOccupiedCell = found;
+
+        if (!found) {
+            final int cellXY[] = mTmpXY;
+            pointToCellExact(x, y, cellXY);
+
+            cellInfo.cell = null;
+            cellInfo.cellX = cellXY[0];
+            cellInfo.cellY = cellXY[1];
+            cellInfo.spanX = 1;
+            cellInfo.spanY = 1;
+        }
+        setTag(cellInfo);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // First we clear the tag to ensure that on every touch down we start with a fresh slate,
+        // even in the case where we return early. Not clearing here was causing bugs whereby on
+        // long-press we'd end up picking up an item from a previous drag operation.
+        final int action = ev.getAction();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            clearTagCellInfo();
+        }
+
+        if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
+            return true;
+        }
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
+        }
+
+        return false;
+    }
+
+    private void clearTagCellInfo() {
+        final CellInfo cellInfo = mCellInfo;
+        cellInfo.cell = null;
+        cellInfo.cellX = -1;
+        cellInfo.cellY = -1;
+        cellInfo.spanX = 0;
+        cellInfo.spanY = 0;
+        setTag(cellInfo);
+    }
+
+    public CellInfo getTag() {
+        return (CellInfo) super.getTag();
+    }
+
+    /**
+     * Given a point, return the cell that strictly encloses that point
+     * @param x X coordinate of the point
+     * @param y Y coordinate of the point
+     * @param result Array of 2 ints to hold the x and y coordinate of the cell
+     */
+    void pointToCellExact(int x, int y, int[] result) {
+        final int hStartPadding = getPaddingLeft();
+        final int vStartPadding = getPaddingTop();
+
+        result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
+        result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+
+        final int xAxis = mCountX;
+        final int yAxis = mCountY;
+
+        if (result[0] < 0) result[0] = 0;
+        if (result[0] >= xAxis) result[0] = xAxis - 1;
+        if (result[1] < 0) result[1] = 0;
+        if (result[1] >= yAxis) result[1] = yAxis - 1;
+    }
+
+    /**
+     * Given a point, return the cell that most closely encloses that point
+     * @param x X coordinate of the point
+     * @param y Y coordinate of the point
+     * @param result Array of 2 ints to hold the x and y coordinate of the cell
+     */
+    void pointToCellRounded(int x, int y, int[] result) {
+        pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
+    }
+
+    /**
+     * Given a cell coordinate, return the point that represents the upper left corner of that cell
+     *
+     * @param cellX X coordinate of the cell
+     * @param cellY Y coordinate of the cell
+     *
+     * @param result Array of 2 ints to hold the x and y coordinate of the point
+     */
+    void cellToPoint(int cellX, int cellY, int[] result) {
+        final int hStartPadding = getPaddingLeft();
+        final int vStartPadding = getPaddingTop();
+
+        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
+        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+    }
+
+    /**
+     * Given a cell coordinate, return the point that represents the center of the cell
+     *
+     * @param cellX X coordinate of the cell
+     * @param cellY Y coordinate of the cell
+     *
+     * @param result Array of 2 ints to hold the x and y coordinate of the point
+     */
+    void cellToCenterPoint(int cellX, int cellY, int[] result) {
+        regionToCenterPoint(cellX, cellY, 1, 1, result);
+    }
+
+    /**
+     * Given a cell coordinate and span return the point that represents the center of the regio
+     *
+     * @param cellX X coordinate of the cell
+     * @param cellY Y coordinate of the cell
+     *
+     * @param result Array of 2 ints to hold the x and y coordinate of the point
+     */
+    void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
+        final int hStartPadding = getPaddingLeft();
+        final int vStartPadding = getPaddingTop();
+        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
+                (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
+        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
+                (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+    }
+
+     /**
+     * Given a cell coordinate and span fills out a corresponding pixel rect
+     *
+     * @param cellX X coordinate of the cell
+     * @param cellY Y coordinate of the cell
+     * @param result Rect in which to write the result
+     */
+     void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
+        final int hStartPadding = getPaddingLeft();
+        final int vStartPadding = getPaddingTop();
+        final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
+        final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
+        result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
+                top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+    }
+
+    public float getDistanceFromCell(float x, float y, int[] cell) {
+        cellToCenterPoint(cell[0], cell[1], mTmpPoint);
+        float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
+                Math.pow(y - mTmpPoint[1], 2));
+        return distance;
+    }
+
+    int getCellWidth() {
+        return mCellWidth;
+    }
+
+    int getCellHeight() {
+        return mCellHeight;
+    }
+
+    int getWidthGap() {
+        return mWidthGap;
+    }
+
+    int getHeightGap() {
+        return mHeightGap;
+    }
+
+    Rect getContentRect(Rect r) {
+        if (r == null) {
+            r = new Rect();
+        }
+        int left = getPaddingLeft();
+        int top = getPaddingTop();
+        int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
+        int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
+        r.set(left, top, right, bottom);
+        return r;
+    }
+
+    /** Return a rect that has the cellWidth/cellHeight (left, top), and
+     * widthGap/heightGap (right, bottom) */
+    static void getMetrics(Rect metrics, int paddedMeasureWidth,
+            int paddedMeasureHeight, int countX, int countY) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
+                grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
+    }
+
+    public void setFixedSize(int width, int height) {
+        mFixedWidth = width;
+        mFixedHeight = height;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
+        int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
+        int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
+        if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
+            int cw = grid.calculateCellWidth(childWidthSize, mCountX);
+            int ch = grid.calculateCellHeight(childHeightSize, mCountY);
+            if (cw != mCellWidth || ch != mCellHeight) {
+                mCellWidth = cw;
+                mCellHeight = ch;
+                mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
+                        mHeightGap, mCountX, mCountY);
+            }
+        }
+
+        int newWidth = childWidthSize;
+        int newHeight = childHeightSize;
+        if (mFixedWidth > 0 && mFixedHeight > 0) {
+            newWidth = mFixedWidth;
+            newHeight = mFixedHeight;
+        } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+        }
+
+        int numWidthGaps = mCountX - 1;
+        int numHeightGaps = mCountY - 1;
+
+        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
+            int hSpace = childWidthSize;
+            int vSpace = childHeightSize;
+            int hFreeSpace = hSpace - (mCountX * mCellWidth);
+            int vFreeSpace = vSpace - (mCountY * mCellHeight);
+            mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
+            mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
+            mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
+                    mHeightGap, mCountX, mCountY);
+        } else {
+            mWidthGap = mOriginalWidthGap;
+            mHeightGap = mOriginalHeightGap;
+        }
+        int count = getChildCount();
+        int maxWidth = 0;
+        int maxHeight = 0;
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
+                    MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
+                    MeasureSpec.EXACTLY);
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+            maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+            maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+        }
+        if (mFixedWidth > 0 && mFixedHeight > 0) {
+            setMeasuredDimension(maxWidth, maxHeight);
+        } else {
+            setMeasuredDimension(widthSize, heightSize);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
+                (mCountX * mCellWidth);
+        int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+        int top = getPaddingTop();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            child.layout(left, top,
+                    left + r - l,
+                    top + b - t);
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        // Expand the background drawing bounds by the padding baked into the background drawable
+        Rect padding = new Rect();
+        mNormalBackground.getPadding(padding);
+        mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
+
+        mForegroundRect.set(mForegroundPadding, mForegroundPadding,
+                w - mForegroundPadding, h - mForegroundPadding);
+    }
+
+    @Override
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
+    }
+
+    @Override
+    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+        mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
+    }
+
+    public float getBackgroundAlpha() {
+        return mBackgroundAlpha;
+    }
+
+    public void setBackgroundAlphaMultiplier(float multiplier) {
+        if (mBackgroundAlphaMultiplier != multiplier) {
+            mBackgroundAlphaMultiplier = multiplier;
+            invalidate();
+        }
+    }
+
+    public float getBackgroundAlphaMultiplier() {
+        return mBackgroundAlphaMultiplier;
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        if (mBackgroundAlpha != alpha) {
+            mBackgroundAlpha = alpha;
+            invalidate();
+        }
+    }
+
+    public void setShortcutAndWidgetAlpha(float alpha) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            getChildAt(i).setAlpha(alpha);
+        }
+    }
+
+    public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
+        if (getChildCount() > 0) {
+            return (ShortcutAndWidgetContainer) getChildAt(0);
+        }
+        return null;
+    }
+
+    public View getChildAt(int x, int y) {
+        return mShortcutsAndWidgets.getChildAt(x, y);
+    }
+
+    public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
+            int delay, boolean permanent, boolean adjustOccupied) {
+        ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
+        boolean[][] occupied = mOccupied;
+        if (!permanent) {
+            occupied = mTmpOccupied;
+        }
+
+        if (clc.indexOfChild(child) != -1) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final ItemInfo info = (ItemInfo) child.getTag();
+
+            // We cancel any existing animations
+            if (mReorderAnimators.containsKey(lp)) {
+                mReorderAnimators.get(lp).cancel();
+                mReorderAnimators.remove(lp);
+            }
+
+            final int oldX = lp.x;
+            final int oldY = lp.y;
+            if (adjustOccupied) {
+                occupied[lp.cellX][lp.cellY] = false;
+                occupied[cellX][cellY] = true;
+            }
+            lp.isLockedToGrid = true;
+            if (permanent) {
+                lp.cellX = info.cellX = cellX;
+                lp.cellY = info.cellY = cellY;
+            } else {
+                lp.tmpCellX = cellX;
+                lp.tmpCellY = cellY;
+            }
+            clc.setupLp(lp);
+            lp.isLockedToGrid = false;
+            final int newX = lp.x;
+            final int newY = lp.y;
+
+            lp.x = oldX;
+            lp.y = oldY;
+
+            // Exit early if we're not actually moving the view
+            if (oldX == newX && oldY == newY) {
+                lp.isLockedToGrid = true;
+                return true;
+            }
+
+            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            va.setDuration(duration);
+            mReorderAnimators.put(lp, va);
+
+            va.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float r = ((Float) animation.getAnimatedValue()).floatValue();
+                    lp.x = (int) ((1 - r) * oldX + r * newX);
+                    lp.y = (int) ((1 - r) * oldY + r * newY);
+                    child.requestLayout();
+                }
+            });
+            va.addListener(new AnimatorListenerAdapter() {
+                boolean cancelled = false;
+                public void onAnimationEnd(Animator animation) {
+                    // If the animation was cancelled, it means that another animation
+                    // has interrupted this one, and we don't want to lock the item into
+                    // place just yet.
+                    if (!cancelled) {
+                        lp.isLockedToGrid = true;
+                        child.requestLayout();
+                    }
+                    if (mReorderAnimators.containsKey(lp)) {
+                        mReorderAnimators.remove(lp);
+                    }
+                }
+                public void onAnimationCancel(Animator animation) {
+                    cancelled = true;
+                }
+            });
+            va.setStartDelay(delay);
+            va.start();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Estimate where the top left cell of the dragged item will land if it is dropped.
+     *
+     * @param originX The X value of the top left corner of the item
+     * @param originY The Y value of the top left corner of the item
+     * @param spanX The number of horizontal cells that the item spans
+     * @param spanY The number of vertical cells that the item spans
+     * @param result The estimated drop cell X and Y.
+     */
+    void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
+        final int countX = mCountX;
+        final int countY = mCountY;
+
+        // pointToCellRounded takes the top left of a cell but will pad that with
+        // cellWidth/2 and cellHeight/2 when finding the matching cell
+        pointToCellRounded(originX, originY, result);
+
+        // If the item isn't fully on this screen, snap to the edges
+        int rightOverhang = result[0] + spanX - countX;
+        if (rightOverhang > 0) {
+            result[0] -= rightOverhang; // Snap to right
+        }
+        result[0] = Math.max(0, result[0]); // Snap to left
+        int bottomOverhang = result[1] + spanY - countY;
+        if (bottomOverhang > 0) {
+            result[1] -= bottomOverhang; // Snap to bottom
+        }
+        result[1] = Math.max(0, result[1]); // Snap to top
+    }
+
+    void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
+            int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
+        final int oldDragCellX = mDragCell[0];
+        final int oldDragCellY = mDragCell[1];
+
+        if (dragOutline == null && v == null) {
+            return;
+        }
+
+        if (cellX != oldDragCellX || cellY != oldDragCellY) {
+            mDragCell[0] = cellX;
+            mDragCell[1] = cellY;
+            // Find the top left corner of the rect the object will occupy
+            final int[] topLeft = mTmpPoint;
+            cellToPoint(cellX, cellY, topLeft);
+
+            int left = topLeft[0];
+            int top = topLeft[1];
+
+            if (v != null && dragOffset == null) {
+                // When drawing the drag outline, it did not account for margin offsets
+                // added by the view's parent.
+                MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
+                left += lp.leftMargin;
+                top += lp.topMargin;
+
+                // Offsets due to the size difference between the View and the dragOutline.
+                // There is a size difference to account for the outer blur, which may lie
+                // outside the bounds of the view.
+                top += (v.getHeight() - dragOutline.getHeight()) / 2;
+                // We center about the x axis
+                left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                        - dragOutline.getWidth()) / 2;
+            } else {
+                if (dragOffset != null && dragRegion != null) {
+                    // Center the drag region *horizontally* in the cell and apply a drag
+                    // outline offset
+                    left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                             - dragRegion.width()) / 2;
+                    int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+                    int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+                    top += dragOffset.y + cellPaddingY;
+                } else {
+                    // Center the drag outline in the cell
+                    left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+                            - dragOutline.getWidth()) / 2;
+                    top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
+                            - dragOutline.getHeight()) / 2;
+                }
+            }
+            final int oldIndex = mDragOutlineCurrent;
+            mDragOutlineAnims[oldIndex].animateOut();
+            mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
+            Rect r = mDragOutlines[mDragOutlineCurrent];
+            r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+            if (resize) {
+                cellToRect(cellX, cellY, spanX, spanY, r);
+            }
+
+            mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
+            mDragOutlineAnims[mDragOutlineCurrent].animateIn();
+        }
+    }
+
+    public void clearDragOutlines() {
+        final int oldIndex = mDragOutlineCurrent;
+        mDragOutlineAnims[oldIndex].animateOut();
+        mDragCell[0] = mDragCell[1] = -1;
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+            int[] result) {
+        return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param minSpanX The minimum horizontal span required
+     * @param minSpanY The minimum vertical span required
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+            int spanY, int[] result, int[] resultSpan) {
+        return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
+                result, resultSpan);
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreOccupied If true, the result can be an occupied cell
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
+            boolean ignoreOccupied, int[] result) {
+        return findNearestArea(pixelX, pixelY, spanX, spanY,
+                spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
+    }
+
+    private final Stack<Rect> mTempRectStack = new Stack<Rect>();
+    private void lazyInitTempRectStack() {
+        if (mTempRectStack.isEmpty()) {
+            for (int i = 0; i < mCountX * mCountY; i++) {
+                mTempRectStack.push(new Rect());
+            }
+        }
+    }
+
+    private void recycleTempRects(Stack<Rect> used) {
+        while (!used.isEmpty()) {
+            mTempRectStack.push(used.pop());
+        }
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param minSpanX The minimum horizontal span required
+     * @param minSpanY The minimum vertical span required
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreOccupied If true, the result can be an occupied cell
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+            View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
+            boolean[][] occupied) {
+        lazyInitTempRectStack();
+        // mark space take by ignoreView as available (method checks if ignoreView is null)
+        markCellsAsUnoccupiedForView(ignoreView, occupied);
+
+        // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
+        // to the center of the item, but we are searching based on the top-left cell, so
+        // we translate the point over to correspond to the top-left.
+        pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
+        pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
+
+        // Keep track of best-scoring drop area
+        final int[] bestXY = result != null ? result : new int[2];
+        double bestDistance = Double.MAX_VALUE;
+        final Rect bestRect = new Rect(-1, -1, -1, -1);
+        final Stack<Rect> validRegions = new Stack<Rect>();
+
+        final int countX = mCountX;
+        final int countY = mCountY;
+
+        if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
+                spanX < minSpanX || spanY < minSpanY) {
+            return bestXY;
+        }
+
+        for (int y = 0; y < countY - (minSpanY - 1); y++) {
+            inner:
+            for (int x = 0; x < countX - (minSpanX - 1); x++) {
+                int ySize = -1;
+                int xSize = -1;
+                if (ignoreOccupied) {
+                    // First, let's see if this thing fits anywhere
+                    for (int i = 0; i < minSpanX; i++) {
+                        for (int j = 0; j < minSpanY; j++) {
+                            if (occupied[x + i][y + j]) {
+                                continue inner;
+                            }
+                        }
+                    }
+                    xSize = minSpanX;
+                    ySize = minSpanY;
+
+                    // We know that the item will fit at _some_ acceptable size, now let's see
+                    // how big we can make it. We'll alternate between incrementing x and y spans
+                    // until we hit a limit.
+                    boolean incX = true;
+                    boolean hitMaxX = xSize >= spanX;
+                    boolean hitMaxY = ySize >= spanY;
+                    while (!(hitMaxX && hitMaxY)) {
+                        if (incX && !hitMaxX) {
+                            for (int j = 0; j < ySize; j++) {
+                                if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
+                                    // We can't move out horizontally
+                                    hitMaxX = true;
+                                }
+                            }
+                            if (!hitMaxX) {
+                                xSize++;
+                            }
+                        } else if (!hitMaxY) {
+                            for (int i = 0; i < xSize; i++) {
+                                if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
+                                    // We can't move out vertically
+                                    hitMaxY = true;
+                                }
+                            }
+                            if (!hitMaxY) {
+                                ySize++;
+                            }
+                        }
+                        hitMaxX |= xSize >= spanX;
+                        hitMaxY |= ySize >= spanY;
+                        incX = !incX;
+                    }
+                    incX = true;
+                    hitMaxX = xSize >= spanX;
+                    hitMaxY = ySize >= spanY;
+                }
+                final int[] cellXY = mTmpXY;
+                cellToCenterPoint(x, y, cellXY);
+
+                // We verify that the current rect is not a sub-rect of any of our previous
+                // candidates. In this case, the current rect is disqualified in favour of the
+                // containing rect.
+                Rect currentRect = mTempRectStack.pop();
+                currentRect.set(x, y, x + xSize, y + ySize);
+                boolean contained = false;
+                for (Rect r : validRegions) {
+                    if (r.contains(currentRect)) {
+                        contained = true;
+                        break;
+                    }
+                }
+                validRegions.push(currentRect);
+                double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
+                        + Math.pow(cellXY[1] - pixelY, 2));
+
+                if ((distance <= bestDistance && !contained) ||
+                        currentRect.contains(bestRect)) {
+                    bestDistance = distance;
+                    bestXY[0] = x;
+                    bestXY[1] = y;
+                    if (resultSpan != null) {
+                        resultSpan[0] = xSize;
+                        resultSpan[1] = ySize;
+                    }
+                    bestRect.set(currentRect);
+                }
+            }
+        }
+        // re-mark space taken by ignoreView as occupied
+        markCellsAsOccupiedForView(ignoreView, occupied);
+
+        // Return -1, -1 if no suitable location found
+        if (bestDistance == Double.MAX_VALUE) {
+            bestXY[0] = -1;
+            bestXY[1] = -1;
+        }
+        recycleTempRects(validRegions);
+        return bestXY;
+    }
+
+     /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location, and will also weigh in a suggested direction vector of the
+     * desired location. This method computers distance based on unit grid distances,
+     * not pixel distances.
+     *
+     * @param cellX The X cell nearest to which you want to search for a vacant area.
+     * @param cellY The Y cell nearest which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param direction The favored direction in which the views should move from x, y
+     * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
+     *        matches exactly. Otherwise we find the best matching direction.
+     * @param occoupied The array which represents which cells in the CellLayout are occupied
+     * @param blockOccupied The array which represents which cells in the specified block (cellX,
+     *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
+            boolean[][] occupied, boolean blockOccupied[][], int[] result) {
+        // Keep track of best-scoring drop area
+        final int[] bestXY = result != null ? result : new int[2];
+        float bestDistance = Float.MAX_VALUE;
+        int bestDirectionScore = Integer.MIN_VALUE;
+
+        final int countX = mCountX;
+        final int countY = mCountY;
+
+        for (int y = 0; y < countY - (spanY - 1); y++) {
+            inner:
+            for (int x = 0; x < countX - (spanX - 1); x++) {
+                // First, let's see if this thing fits anywhere
+                for (int i = 0; i < spanX; i++) {
+                    for (int j = 0; j < spanY; j++) {
+                        if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
+                            continue inner;
+                        }
+                    }
+                }
+
+                float distance = (float)
+                        Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
+                int[] curDirection = mTmpPoint;
+                computeDirectionVector(x - cellX, y - cellY, curDirection);
+                // The direction score is just the dot product of the two candidate direction
+                // and that passed in.
+                int curDirectionScore = direction[0] * curDirection[0] +
+                        direction[1] * curDirection[1];
+                boolean exactDirectionOnly = false;
+                boolean directionMatches = direction[0] == curDirection[0] &&
+                        direction[0] == curDirection[0];
+                if ((directionMatches || !exactDirectionOnly) &&
+                        Float.compare(distance,  bestDistance) < 0 || (Float.compare(distance,
+                        bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
+                    bestDistance = distance;
+                    bestDirectionScore = curDirectionScore;
+                    bestXY[0] = x;
+                    bestXY[1] = y;
+                }
+            }
+        }
+
+        // Return -1, -1 if no suitable location found
+        if (bestDistance == Float.MAX_VALUE) {
+            bestXY[0] = -1;
+            bestXY[1] = -1;
+        }
+        return bestXY;
+    }
+
+    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
+            int[] direction, ItemConfiguration currentState) {
+        CellAndSpan c = currentState.map.get(v);
+        boolean success = false;
+        markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+        markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+        findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
+
+        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+            c.x = mTempLocation[0];
+            c.y = mTempLocation[1];
+            success = true;
+        }
+        markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+        return success;
+    }
+
+    /**
+     * This helper class defines a cluster of views. It helps with defining complex edges
+     * of the cluster and determining how those edges interact with other views. The edges
+     * essentially define a fine-grained boundary around the cluster of views -- like a more
+     * precise version of a bounding box.
+     */
+    private class ViewCluster {
+        final static int LEFT = 0;
+        final static int TOP = 1;
+        final static int RIGHT = 2;
+        final static int BOTTOM = 3;
+
+        ArrayList<View> views;
+        ItemConfiguration config;
+        Rect boundingRect = new Rect();
+
+        int[] leftEdge = new int[mCountY];
+        int[] rightEdge = new int[mCountY];
+        int[] topEdge = new int[mCountX];
+        int[] bottomEdge = new int[mCountX];
+        boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
+
+        @SuppressWarnings("unchecked")
+        public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
+            this.views = (ArrayList<View>) views.clone();
+            this.config = config;
+            resetEdges();
+        }
+
+        void resetEdges() {
+            for (int i = 0; i < mCountX; i++) {
+                topEdge[i] = -1;
+                bottomEdge[i] = -1;
+            }
+            for (int i = 0; i < mCountY; i++) {
+                leftEdge[i] = -1;
+                rightEdge[i] = -1;
+            }
+            leftEdgeDirty = true;
+            rightEdgeDirty = true;
+            bottomEdgeDirty = true;
+            topEdgeDirty = true;
+            boundingRectDirty = true;
+        }
+
+        void computeEdge(int which, int[] edge) {
+            int count = views.size();
+            for (int i = 0; i < count; i++) {
+                CellAndSpan cs = config.map.get(views.get(i));
+                switch (which) {
+                    case LEFT:
+                        int left = cs.x;
+                        for (int j = cs.y; j < cs.y + cs.spanY; j++) {
+                            if (left < edge[j] || edge[j] < 0) {
+                                edge[j] = left;
+                            }
+                        }
+                        break;
+                    case RIGHT:
+                        int right = cs.x + cs.spanX;
+                        for (int j = cs.y; j < cs.y + cs.spanY; j++) {
+                            if (right > edge[j]) {
+                                edge[j] = right;
+                            }
+                        }
+                        break;
+                    case TOP:
+                        int top = cs.y;
+                        for (int j = cs.x; j < cs.x + cs.spanX; j++) {
+                            if (top < edge[j] || edge[j] < 0) {
+                                edge[j] = top;
+                            }
+                        }
+                        break;
+                    case BOTTOM:
+                        int bottom = cs.y + cs.spanY;
+                        for (int j = cs.x; j < cs.x + cs.spanX; j++) {
+                            if (bottom > edge[j]) {
+                                edge[j] = bottom;
+                            }
+                        }
+                        break;
+                }
+            }
+        }
+
+        boolean isViewTouchingEdge(View v, int whichEdge) {
+            CellAndSpan cs = config.map.get(v);
+
+            int[] edge = getEdge(whichEdge);
+
+            switch (whichEdge) {
+                case LEFT:
+                    for (int i = cs.y; i < cs.y + cs.spanY; i++) {
+                        if (edge[i] == cs.x + cs.spanX) {
+                            return true;
+                        }
+                    }
+                    break;
+                case RIGHT:
+                    for (int i = cs.y; i < cs.y + cs.spanY; i++) {
+                        if (edge[i] == cs.x) {
+                            return true;
+                        }
+                    }
+                    break;
+                case TOP:
+                    for (int i = cs.x; i < cs.x + cs.spanX; i++) {
+                        if (edge[i] == cs.y + cs.spanY) {
+                            return true;
+                        }
+                    }
+                    break;
+                case BOTTOM:
+                    for (int i = cs.x; i < cs.x + cs.spanX; i++) {
+                        if (edge[i] == cs.y) {
+                            return true;
+                        }
+                    }
+                    break;
+            }
+            return false;
+        }
+
+        void shift(int whichEdge, int delta) {
+            for (View v: views) {
+                CellAndSpan c = config.map.get(v);
+                switch (whichEdge) {
+                    case LEFT:
+                        c.x -= delta;
+                        break;
+                    case RIGHT:
+                        c.x += delta;
+                        break;
+                    case TOP:
+                        c.y -= delta;
+                        break;
+                    case BOTTOM:
+                    default:
+                        c.y += delta;
+                        break;
+                }
+            }
+            resetEdges();
+        }
+
+        public void addView(View v) {
+            views.add(v);
+            resetEdges();
+        }
+
+        public Rect getBoundingRect() {
+            if (boundingRectDirty) {
+                boolean first = true;
+                for (View v: views) {
+                    CellAndSpan c = config.map.get(v);
+                    if (first) {
+                        boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+                        first = false;
+                    } else {
+                        boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+                    }
+                }
+            }
+            return boundingRect;
+        }
+
+        public int[] getEdge(int which) {
+            switch (which) {
+                case LEFT:
+                    return getLeftEdge();
+                case RIGHT:
+                    return getRightEdge();
+                case TOP:
+                    return getTopEdge();
+                case BOTTOM:
+                default:
+                    return getBottomEdge();
+            }
+        }
+
+        public int[] getLeftEdge() {
+            if (leftEdgeDirty) {
+                computeEdge(LEFT, leftEdge);
+            }
+            return leftEdge;
+        }
+
+        public int[] getRightEdge() {
+            if (rightEdgeDirty) {
+                computeEdge(RIGHT, rightEdge);
+            }
+            return rightEdge;
+        }
+
+        public int[] getTopEdge() {
+            if (topEdgeDirty) {
+                computeEdge(TOP, topEdge);
+            }
+            return topEdge;
+        }
+
+        public int[] getBottomEdge() {
+            if (bottomEdgeDirty) {
+                computeEdge(BOTTOM, bottomEdge);
+            }
+            return bottomEdge;
+        }
+
+        PositionComparator comparator = new PositionComparator();
+        class PositionComparator implements Comparator<View> {
+            int whichEdge = 0;
+            public int compare(View left, View right) {
+                CellAndSpan l = config.map.get(left);
+                CellAndSpan r = config.map.get(right);
+                switch (whichEdge) {
+                    case LEFT:
+                        return (r.x + r.spanX) - (l.x + l.spanX);
+                    case RIGHT:
+                        return l.x - r.x;
+                    case TOP:
+                        return (r.y + r.spanY) - (l.y + l.spanY);
+                    case BOTTOM:
+                    default:
+                        return l.y - r.y;
+                }
+            }
+        }
+
+        public void sortConfigurationForEdgePush(int edge) {
+            comparator.whichEdge = edge;
+            Collections.sort(config.sortedViews, comparator);
+        }
+    }
+
+    private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+            int[] direction, View dragView, ItemConfiguration currentState) {
+
+        ViewCluster cluster = new ViewCluster(views, currentState);
+        Rect clusterRect = cluster.getBoundingRect();
+        int whichEdge;
+        int pushDistance;
+        boolean fail = false;
+
+        // Determine the edge of the cluster that will be leading the push and how far
+        // the cluster must be shifted.
+        if (direction[0] < 0) {
+            whichEdge = ViewCluster.LEFT;
+            pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
+        } else if (direction[0] > 0) {
+            whichEdge = ViewCluster.RIGHT;
+            pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
+        } else if (direction[1] < 0) {
+            whichEdge = ViewCluster.TOP;
+            pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
+        } else {
+            whichEdge = ViewCluster.BOTTOM;
+            pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
+        }
+
+        // Break early for invalid push distance.
+        if (pushDistance <= 0) {
+            return false;
+        }
+
+        // Mark the occupied state as false for the group of views we want to move.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+        }
+
+        // We save the current configuration -- if we fail to find a solution we will revert
+        // to the initial state. The process of finding a solution modifies the configuration
+        // in place, hence the need for revert in the failure case.
+        currentState.save();
+
+        // The pushing algorithm is simplified by considering the views in the order in which
+        // they would be pushed by the cluster. For example, if the cluster is leading with its
+        // left edge, we consider sort the views by their right edge, from right to left.
+        cluster.sortConfigurationForEdgePush(whichEdge);
+
+        while (pushDistance > 0 && !fail) {
+            for (View v: currentState.sortedViews) {
+                // For each view that isn't in the cluster, we see if the leading edge of the
+                // cluster is contacting the edge of that view. If so, we add that view to the
+                // cluster.
+                if (!cluster.views.contains(v) && v != dragView) {
+                    if (cluster.isViewTouchingEdge(v, whichEdge)) {
+                        LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                        if (!lp.canReorder) {
+                            // The push solution includes the all apps button, this is not viable.
+                            fail = true;
+                            break;
+                        }
+                        cluster.addView(v);
+                        CellAndSpan c = currentState.map.get(v);
+
+                        // Adding view to cluster, mark it as not occupied.
+                        markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+                    }
+                }
+            }
+            pushDistance--;
+
+            // The cluster has been completed, now we move the whole thing over in the appropriate
+            // direction.
+            cluster.shift(whichEdge, 1);
+        }
+
+        boolean foundSolution = false;
+        clusterRect = cluster.getBoundingRect();
+
+        // Due to the nature of the algorithm, the only check required to verify a valid solution
+        // is to ensure that completed shifted cluster lies completely within the cell layout.
+        if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
+                clusterRect.bottom <= mCountY) {
+            foundSolution = true;
+        } else {
+            currentState.restore();
+        }
+
+        // In either case, we set the occupied array as marked for the location of the views
+        for (View v: cluster.views) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+        }
+
+        return foundSolution;
+    }
+
+    private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+            int[] direction, View dragView, ItemConfiguration currentState) {
+        if (views.size() == 0) return true;
+
+        boolean success = false;
+        Rect boundingRect = null;
+        // We construct a rect which represents the entire group of views passed in
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            if (boundingRect == null) {
+                boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+            } else {
+                boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+            }
+        }
+
+        // Mark the occupied state as false for the group of views we want to move.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+        }
+
+        boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
+        int top = boundingRect.top;
+        int left = boundingRect.left;
+        // We mark more precisely which parts of the bounding rect are truly occupied, allowing
+        // for interlocking.
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
+        }
+
+        markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+        findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
+                boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+
+        // If we successfuly found a location by pushing the block of views, we commit it
+        if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+            int deltaX = mTempLocation[0] - boundingRect.left;
+            int deltaY = mTempLocation[1] - boundingRect.top;
+            for (View v: views) {
+                CellAndSpan c = currentState.map.get(v);
+                c.x += deltaX;
+                c.y += deltaY;
+            }
+            success = true;
+        }
+
+        // In either case, we set the occupied array as marked for the location of the views
+        for (View v: views) {
+            CellAndSpan c = currentState.map.get(v);
+            markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+        }
+        return success;
+    }
+
+    private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
+        markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
+    }
+
+    // This method tries to find a reordering solution which satisfies the push mechanic by trying
+    // to push items in each of the cardinal directions, in an order based on the direction vector
+    // passed.
+    private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
+            int[] direction, View ignoreView, ItemConfiguration solution) {
+        if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
+            // If the direction vector has two non-zero components, we try pushing
+            // separately in each of the components.
+            int temp = direction[1];
+            direction[1] = 0;
+
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            direction[1] = temp;
+            temp = direction[0];
+            direction[0] = 0;
+
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Revert the direction
+            direction[0] = temp;
+
+            // Now we try pushing in each component of the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            temp = direction[1];
+            direction[1] = 0;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+
+            direction[1] = temp;
+            temp = direction[0];
+            direction[0] = 0;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // revert the direction
+            direction[0] = temp;
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+        } else {
+            // If the direction vector has a single non-zero component, we push first in the
+            // direction of the vector
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Then we try the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Switch the direction back
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+            // If we have failed to find a push solution with the above, then we try
+            // to find a solution by pushing along the perpendicular axis.
+
+            // Swap the components
+            int temp = direction[1];
+            direction[1] = direction[0];
+            direction[0] = temp;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+
+            // Then we try the opposite direction
+            direction[0] *= -1;
+            direction[1] *= -1;
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+                    ignoreView, solution)) {
+                return true;
+            }
+            // Switch the direction back
+            direction[0] *= -1;
+            direction[1] *= -1;
+
+            // Swap the components back
+            temp = direction[1];
+            direction[1] = direction[0];
+            direction[0] = temp;
+        }
+        return false;
+    }
+
+    private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+            View ignoreView, ItemConfiguration solution) {
+        // Return early if get invalid cell positions
+        if (cellX < 0 || cellY < 0) return false;
+
+        mIntersectingViews.clear();
+        mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+
+        // Mark the desired location of the view currently being dragged.
+        if (ignoreView != null) {
+            CellAndSpan c = solution.map.get(ignoreView);
+            if (c != null) {
+                c.x = cellX;
+                c.y = cellY;
+            }
+        }
+        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+        Rect r1 = new Rect();
+        for (View child: solution.map.keySet()) {
+            if (child == ignoreView) continue;
+            CellAndSpan c = solution.map.get(child);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+            if (Rect.intersects(r0, r1)) {
+                if (!lp.canReorder) {
+                    return false;
+                }
+                mIntersectingViews.add(child);
+            }
+        }
+
+        // First we try to find a solution which respects the push mechanic. That is,
+        // we try to find a solution such that no displaced item travels through another item
+        // without also displacing that item.
+        if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Next we try moving the views as a block, but without requiring the push mechanic.
+        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Ok, they couldn't move as a block, let's move them individually
+        for (View v : mIntersectingViews) {
+            if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
+     * the provided point and the provided cell
+     */
+    private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
+        double angle = Math.atan(((float) deltaY) / deltaX);
+
+        result[0] = 0;
+        result[1] = 0;
+        if (Math.abs(Math.cos(angle)) > 0.5f) {
+            result[0] = (int) Math.signum(deltaX);
+        }
+        if (Math.abs(Math.sin(angle)) > 0.5f) {
+            result[1] = (int) Math.signum(deltaY);
+        }
+    }
+
+    private void copyOccupiedArray(boolean[][] occupied) {
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                occupied[i][j] = mOccupied[i][j];
+            }
+        }
+    }
+
+    ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+            int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
+        // Copy the current state into the solution. This solution will be manipulated as necessary.
+        copyCurrentStateToSolution(solution, false);
+        // Copy the current occupied array into the temporary occupied array. This array will be
+        // manipulated as necessary to find a solution.
+        copyOccupiedArray(mTmpOccupied);
+
+        // We find the nearest cell into which we would place the dragged item, assuming there's
+        // nothing in its way.
+        int result[] = new int[2];
+        result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+        boolean success = false;
+        // First we try the exact nearest position of the item being dragged,
+        // we will then want to try to move this around to other neighbouring positions
+        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+                solution);
+
+        if (!success) {
+            // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
+            // x, then 1 in y etc.
+            if (spanX > minSpanX && (minSpanY == spanY || decX)) {
+                return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
+                        dragView, false, solution);
+            } else if (spanY > minSpanY) {
+                return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
+                        dragView, true, solution);
+            }
+            solution.isSolution = false;
+        } else {
+            solution.isSolution = true;
+            solution.dragViewX = result[0];
+            solution.dragViewY = result[1];
+            solution.dragViewSpanX = spanX;
+            solution.dragViewSpanY = spanY;
+        }
+        return solution;
+    }
+
+    private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellAndSpan c;
+            if (temp) {
+                c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
+            } else {
+                c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
+            }
+            solution.add(child, c);
+        }
+    }
+
+    private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                mTmpOccupied[i][j] = false;
+            }
+        }
+
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            if (child == dragView) continue;
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellAndSpan c = solution.map.get(child);
+            if (c != null) {
+                lp.tmpCellX = c.x;
+                lp.tmpCellY = c.y;
+                lp.cellHSpan = c.spanX;
+                lp.cellVSpan = c.spanY;
+                markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+            }
+        }
+        markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+                solution.dragViewSpanY, mTmpOccupied, true);
+    }
+
+    private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
+            commitDragView) {
+
+        boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                occupied[i][j] = false;
+            }
+        }
+
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            if (child == dragView) continue;
+            CellAndSpan c = solution.map.get(child);
+            if (c != null) {
+                animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
+                        DESTRUCTIVE_REORDER, false);
+                markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
+            }
+        }
+        if (commitDragView) {
+            markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+                    solution.dragViewSpanY, occupied, true);
+        }
+    }
+
+    // This method starts or changes the reorder hint animations
+    private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            if (child == dragView) continue;
+            CellAndSpan c = solution.map.get(child);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (c != null) {
+                ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
+                        c.x, c.y, c.spanX, c.spanY);
+                rha.animate();
+            }
+        }
+    }
+
+    // Class which represents the reorder hint animations. These animations show that an item is
+    // in a temporary state, and hint at where the item will return to.
+    class ReorderHintAnimation {
+        View child;
+        float finalDeltaX;
+        float finalDeltaY;
+        float initDeltaX;
+        float initDeltaY;
+        float finalScale;
+        float initScale;
+        private static final int DURATION = 300;
+        Animator a;
+
+        public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
+                int spanX, int spanY) {
+            regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
+            final int x0 = mTmpPoint[0];
+            final int y0 = mTmpPoint[1];
+            regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
+            final int x1 = mTmpPoint[0];
+            final int y1 = mTmpPoint[1];
+            final int dX = x1 - x0;
+            final int dY = y1 - y0;
+            finalDeltaX = 0;
+            finalDeltaY = 0;
+            if (dX == dY && dX == 0) {
+            } else {
+                if (dY == 0) {
+                    finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
+                } else if (dX == 0) {
+                    finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
+                } else {
+                    double angle = Math.atan( (float) (dY) / dX);
+                    finalDeltaX = (int) (- Math.signum(dX) *
+                            Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
+                    finalDeltaY = (int) (- Math.signum(dY) *
+                            Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
+                }
+            }
+            initDeltaX = child.getTranslationX();
+            initDeltaY = child.getTranslationY();
+            finalScale = getChildrenScale() - 4.0f / child.getWidth();
+            initScale = child.getScaleX();
+            this.child = child;
+        }
+
+        void animate() {
+            if (mShakeAnimators.containsKey(child)) {
+                ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
+                oldAnimation.cancel();
+                mShakeAnimators.remove(child);
+                if (finalDeltaX == 0 && finalDeltaY == 0) {
+                    completeAnimationImmediately();
+                    return;
+                }
+            }
+            if (finalDeltaX == 0 && finalDeltaY == 0) {
+                return;
+            }
+            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            a = va;
+            va.setRepeatMode(ValueAnimator.REVERSE);
+            va.setRepeatCount(ValueAnimator.INFINITE);
+            va.setDuration(DURATION);
+            va.setStartDelay((int) (Math.random() * 60));
+            va.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float r = ((Float) animation.getAnimatedValue()).floatValue();
+                    float x = r * finalDeltaX + (1 - r) * initDeltaX;
+                    float y = r * finalDeltaY + (1 - r) * initDeltaY;
+                    child.setTranslationX(x);
+                    child.setTranslationY(y);
+                    float s = r * finalScale + (1 - r) * initScale;
+                    child.setScaleX(s);
+                    child.setScaleY(s);
+                }
+            });
+            va.addListener(new AnimatorListenerAdapter() {
+                public void onAnimationRepeat(Animator animation) {
+                    // We make sure to end only after a full period
+                    initDeltaX = 0;
+                    initDeltaY = 0;
+                    initScale = getChildrenScale();
+                }
+            });
+            mShakeAnimators.put(child, this);
+            va.start();
+        }
+
+        private void cancel() {
+            if (a != null) {
+                a.cancel();
+            }
+        }
+
+        private void completeAnimationImmediately() {
+            if (a != null) {
+                a.cancel();
+            }
+
+            AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
+            a = s;
+            s.playTogether(
+                LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
+                LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
+                LauncherAnimUtils.ofFloat(child, "translationX", 0f),
+                LauncherAnimUtils.ofFloat(child, "translationY", 0f)
+            );
+            s.setDuration(REORDER_ANIMATION_DURATION);
+            s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+            s.start();
+        }
+    }
+
+    private void completeAndClearReorderHintAnimations() {
+        for (ReorderHintAnimation a: mShakeAnimators.values()) {
+            a.completeAnimationImmediately();
+        }
+        mShakeAnimators.clear();
+    }
+
+    private void commitTempPlacement() {
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                mOccupied[i][j] = mTmpOccupied[i][j];
+            }
+        }
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            ItemInfo info = (ItemInfo) child.getTag();
+            // We do a null check here because the item info can be null in the case of the
+            // AllApps button in the hotseat.
+            if (info != null) {
+                if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
+                        info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
+                    info.requiresDbUpdate = true;
+                }
+                info.cellX = lp.cellX = lp.tmpCellX;
+                info.cellY = lp.cellY = lp.tmpCellY;
+                info.spanX = lp.cellHSpan;
+                info.spanY = lp.cellVSpan;
+            }
+        }
+        mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
+    }
+
+    public void setUseTempCoords(boolean useTempCoords) {
+        int childCount = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
+            lp.useTmpCoords = useTempCoords;
+        }
+    }
+
+    ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
+            int spanX, int spanY, View dragView, ItemConfiguration solution) {
+        int[] result = new int[2];
+        int[] resultSpan = new int[2];
+        findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
+                resultSpan);
+        if (result[0] >= 0 && result[1] >= 0) {
+            copyCurrentStateToSolution(solution, false);
+            solution.dragViewX = result[0];
+            solution.dragViewY = result[1];
+            solution.dragViewSpanX = resultSpan[0];
+            solution.dragViewSpanY = resultSpan[1];
+            solution.isSolution = true;
+        } else {
+            solution.isSolution = false;
+        }
+        return solution;
+    }
+
+    public void prepareChildForDrag(View child) {
+        markCellsAsUnoccupiedForView(child);
+    }
+
+    /* This seems like it should be obvious and straight-forward, but when the direction vector
+    needs to match with the notion of the dragView pushing other views, we have to employ
+    a slightly more subtle notion of the direction vector. The question is what two points is
+    the vector between? The center of the dragView and its desired destination? Not quite, as
+    this doesn't necessarily coincide with the interaction of the dragView and items occupying
+    those cells. Instead we use some heuristics to often lock the vector to up, down, left
+    or right, which helps make pushing feel right.
+    */
+    private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
+            int spanY, View dragView, int[] resultDirection) {
+        int[] targetDestination = new int[2];
+
+        findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
+        Rect dragRect = new Rect();
+        regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+        dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
+
+        Rect dropRegionRect = new Rect();
+        getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
+                dragView, dropRegionRect, mIntersectingViews);
+
+        int dropRegionSpanX = dropRegionRect.width();
+        int dropRegionSpanY = dropRegionRect.height();
+
+        regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+                dropRegionRect.height(), dropRegionRect);
+
+        int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
+        int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
+
+        if (dropRegionSpanX == mCountX || spanX == mCountX) {
+            deltaX = 0;
+        }
+        if (dropRegionSpanY == mCountY || spanY == mCountY) {
+            deltaY = 0;
+        }
+
+        if (deltaX == 0 && deltaY == 0) {
+            // No idea what to do, give a random direction.
+            resultDirection[0] = 1;
+            resultDirection[1] = 0;
+        } else {
+            computeDirectionVector(deltaX, deltaY, resultDirection);
+        }
+    }
+
+    // For a given cell and span, fetch the set of views intersecting the region.
+    private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
+            View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
+        if (boundingRect != null) {
+            boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+        }
+        intersectingViews.clear();
+        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+        Rect r1 = new Rect();
+        final int count = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            if (child == dragView) continue;
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
+            if (Rect.intersects(r0, r1)) {
+                mIntersectingViews.add(child);
+                if (boundingRect != null) {
+                    boundingRect.union(r1);
+                }
+            }
+        }
+    }
+
+    boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
+            View dragView, int[] result) {
+        result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+        getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
+                mIntersectingViews);
+        return !mIntersectingViews.isEmpty();
+    }
+
+    void revertTempState() {
+        if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
+        final int count = mShortcutsAndWidgets.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = mShortcutsAndWidgets.getChildAt(i);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
+                lp.tmpCellX = lp.cellX;
+                lp.tmpCellY = lp.cellY;
+                animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
+                        0, false, false);
+            }
+        }
+        completeAndClearReorderHintAnimations();
+        setItemPlacementDirty(false);
+    }
+
+    boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
+            View dragView, int[] direction, boolean commit) {
+        int[] pixelXY = new int[2];
+        regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
+
+        // First we determine if things have moved enough to cause a different layout
+        ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
+                 spanX,  spanY, direction, dragView,  true,  new ItemConfiguration());
+
+        setUseTempCoords(true);
+        if (swapSolution != null && swapSolution.isSolution) {
+            // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
+            // committing anything or animating anything as we just want to determine if a solution
+            // exists
+            copySolutionToTempState(swapSolution, dragView);
+            setItemPlacementDirty(true);
+            animateItemsToSolution(swapSolution, dragView, commit);
+
+            if (commit) {
+                commitTempPlacement();
+                completeAndClearReorderHintAnimations();
+                setItemPlacementDirty(false);
+            } else {
+                beginOrAdjustHintAnimations(swapSolution, dragView,
+                        REORDER_ANIMATION_DURATION);
+            }
+            mShortcutsAndWidgets.requestLayout();
+        }
+        return swapSolution.isSolution;
+    }
+
+    int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+            View dragView, int[] result, int resultSpan[], int mode) {
+        // First we determine if things have moved enough to cause a different layout
+        result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+        if (resultSpan == null) {
+            resultSpan = new int[2];
+        }
+
+        // When we are checking drop validity or actually dropping, we don't recompute the
+        // direction vector, since we want the solution to match the preview, and it's possible
+        // that the exact position of the item has changed to result in a new reordering outcome.
+        if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
+               && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
+            mDirectionVector[0] = mPreviousReorderDirection[0];
+            mDirectionVector[1] = mPreviousReorderDirection[1];
+            // We reset this vector after drop
+            if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+                mPreviousReorderDirection[0] = INVALID_DIRECTION;
+                mPreviousReorderDirection[1] = INVALID_DIRECTION;
+            }
+        } else {
+            getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
+            mPreviousReorderDirection[0] = mDirectionVector[0];
+            mPreviousReorderDirection[1] = mDirectionVector[1];
+        }
+
+        ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
+                 spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());
+
+        // We attempt the approach which doesn't shuffle views at all
+        ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
+                minSpanY, spanX, spanY, dragView, new ItemConfiguration());
+
+        ItemConfiguration finalSolution = null;
+        if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
+            finalSolution = swapSolution;
+        } else if (noShuffleSolution.isSolution) {
+            finalSolution = noShuffleSolution;
+        }
+
+        boolean foundSolution = true;
+        if (!DESTRUCTIVE_REORDER) {
+            setUseTempCoords(true);
+        }
+
+        if (finalSolution != null) {
+            result[0] = finalSolution.dragViewX;
+            result[1] = finalSolution.dragViewY;
+            resultSpan[0] = finalSolution.dragViewSpanX;
+            resultSpan[1] = finalSolution.dragViewSpanY;
+
+            // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
+            // committing anything or animating anything as we just want to determine if a solution
+            // exists
+            if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+                if (!DESTRUCTIVE_REORDER) {
+                    copySolutionToTempState(finalSolution, dragView);
+                }
+                setItemPlacementDirty(true);
+                animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
+
+                if (!DESTRUCTIVE_REORDER &&
+                        (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
+                    commitTempPlacement();
+                    completeAndClearReorderHintAnimations();
+                    setItemPlacementDirty(false);
+                } else {
+                    beginOrAdjustHintAnimations(finalSolution, dragView,
+                            REORDER_ANIMATION_DURATION);
+                }
+            }
+        } else {
+            foundSolution = false;
+            result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+        }
+
+        if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
+            setUseTempCoords(false);
+        }
+
+        mShortcutsAndWidgets.requestLayout();
+        return result;
+    }
+
+    void setItemPlacementDirty(boolean dirty) {
+        mItemPlacementDirty = dirty;
+    }
+    boolean isItemPlacementDirty() {
+        return mItemPlacementDirty;
+    }
+
+    private class ItemConfiguration {
+        HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
+        private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
+        ArrayList<View> sortedViews = new ArrayList<View>();
+        boolean isSolution = false;
+        int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
+
+        void save() {
+            // Copy current state into savedMap
+            for (View v: map.keySet()) {
+                map.get(v).copy(savedMap.get(v));
+            }
+        }
+
+        void restore() {
+            // Restore current state from savedMap
+            for (View v: savedMap.keySet()) {
+                savedMap.get(v).copy(map.get(v));
+            }
+        }
+
+        void add(View v, CellAndSpan cs) {
+            map.put(v, cs);
+            savedMap.put(v, new CellAndSpan());
+            sortedViews.add(v);
+        }
+
+        int area() {
+            return dragViewSpanX * dragViewSpanY;
+        }
+    }
+
+    private class CellAndSpan {
+        int x, y;
+        int spanX, spanY;
+
+        public CellAndSpan() {
+        }
+
+        public void copy(CellAndSpan copy) {
+            copy.x = x;
+            copy.y = y;
+            copy.spanX = spanX;
+            copy.spanY = spanY;
+        }
+
+        public CellAndSpan(int x, int y, int spanX, int spanY) {
+            this.x = x;
+            this.y = y;
+            this.spanX = spanX;
+            this.spanY = spanY;
+        }
+
+        public String toString() {
+            return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
+        }
+
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreView Considers space occupied by this view as unoccupied
+     * @param result Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestVacantArea(
+            int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
+        return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param minSpanX The minimum horizontal span required
+     * @param minSpanY The minimum vertical span required
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreView Considers space occupied by this view as unoccupied
+     * @param result Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
+            int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
+        return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
+                result, resultSpan, mOccupied);
+    }
+
+    /**
+     * Find a starting cell position that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreView Considers space occupied by this view as unoccupied
+     * @param result Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestArea(
+            int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+        return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
+    }
+
+    boolean existsEmptyCell() {
+        return findCellForSpan(null, 1, 1);
+    }
+
+    /**
+     * Finds the upper-left coordinate of the first rectangle in the grid that can
+     * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
+     * then this method will only return coordinates for rectangles that contain the cell
+     * (intersectX, intersectY)
+     *
+     * @param cellXY The array that will contain the position of a vacant cell if such a cell
+     *               can be found.
+     * @param spanX The horizontal span of the cell we want to find.
+     * @param spanY The vertical span of the cell we want to find.
+     *
+     * @return True if a vacant cell of the specified dimension was found, false otherwise.
+     */
+    boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+        return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
+    }
+
+    /**
+     * Like above, but ignores any cells occupied by the item "ignoreView"
+     *
+     * @param cellXY The array that will contain the position of a vacant cell if such a cell
+     *               can be found.
+     * @param spanX The horizontal span of the cell we want to find.
+     * @param spanY The vertical span of the cell we want to find.
+     * @param ignoreView The home screen item we should treat as not occupying any space
+     * @return
+     */
+    boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
+        return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
+                ignoreView, mOccupied);
+    }
+
+    /**
+     * Like above, but if intersectX and intersectY are not -1, then this method will try to
+     * return coordinates for rectangles that contain the cell [intersectX, intersectY]
+     *
+     * @param spanX The horizontal span of the cell we want to find.
+     * @param spanY The vertical span of the cell we want to find.
+     * @param ignoreView The home screen item we should treat as not occupying any space
+     * @param intersectX The X coordinate of the cell that we should try to overlap
+     * @param intersectX The Y coordinate of the cell that we should try to overlap
+     *
+     * @return True if a vacant cell of the specified dimension was found, false otherwise.
+     */
+    boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
+            int intersectX, int intersectY) {
+        return findCellForSpanThatIntersectsIgnoring(
+                cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
+    }
+
+    /**
+     * The superset of the above two methods
+     */
+    boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
+            int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
+        // mark space take by ignoreView as available (method checks if ignoreView is null)
+        markCellsAsUnoccupiedForView(ignoreView, occupied);
+
+        boolean foundCell = false;
+        while (true) {
+            int startX = 0;
+            if (intersectX >= 0) {
+                startX = Math.max(startX, intersectX - (spanX - 1));
+            }
+            int endX = mCountX - (spanX - 1);
+            if (intersectX >= 0) {
+                endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
+            }
+            int startY = 0;
+            if (intersectY >= 0) {
+                startY = Math.max(startY, intersectY - (spanY - 1));
+            }
+            int endY = mCountY - (spanY - 1);
+            if (intersectY >= 0) {
+                endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
+            }
+
+            for (int y = startY; y < endY && !foundCell; y++) {
+                inner:
+                for (int x = startX; x < endX; x++) {
+                    for (int i = 0; i < spanX; i++) {
+                        for (int j = 0; j < spanY; j++) {
+                            if (occupied[x + i][y + j]) {
+                                // small optimization: we can skip to after the column we just found
+                                // an occupied cell
+                                x += i;
+                                continue inner;
+                            }
+                        }
+                    }
+                    if (cellXY != null) {
+                        cellXY[0] = x;
+                        cellXY[1] = y;
+                    }
+                    foundCell = true;
+                    break;
+                }
+            }
+            if (intersectX == -1 && intersectY == -1) {
+                break;
+            } else {
+                // if we failed to find anything, try again but without any requirements of
+                // intersecting
+                intersectX = -1;
+                intersectY = -1;
+                continue;
+            }
+        }
+
+        // re-mark space taken by ignoreView as occupied
+        markCellsAsOccupiedForView(ignoreView, occupied);
+        return foundCell;
+    }
+
+    /**
+     * A drag event has begun over this layout.
+     * It may have begun over this layout (in which case onDragChild is called first),
+     * or it may have begun on another layout.
+     */
+    void onDragEnter() {
+        mDragEnforcer.onDragEnter();
+        mDragging = true;
+    }
+
+    /**
+     * Called when drag has left this CellLayout or has been completed (successfully or not)
+     */
+    void onDragExit() {
+        mDragEnforcer.onDragExit();
+        // This can actually be called when we aren't in a drag, e.g. when adding a new
+        // item to this layout via the customize drawer.
+        // Guard against that case.
+        if (mDragging) {
+            mDragging = false;
+        }
+
+        // Invalidate the drag data
+        mDragCell[0] = mDragCell[1] = -1;
+        mDragOutlineAnims[mDragOutlineCurrent].animateOut();
+        mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
+        revertTempState();
+        setIsDragOverlapping(false);
+    }
+
+    /**
+     * Mark a child as having been dropped.
+     * At the beginning of the drag operation, the child may have been on another
+     * screen, but it is re-parented before this method is called.
+     *
+     * @param child The child that is being dropped
+     */
+    void onDropChild(View child) {
+        if (child != null) {
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.dropped = true;
+            child.requestLayout();
+        }
+    }
+
+    /**
+     * Computes a bounding rectangle for a range of cells
+     *
+     * @param cellX X coordinate of upper left corner expressed as a cell position
+     * @param cellY Y coordinate of upper left corner expressed as a cell position
+     * @param cellHSpan Width in cells
+     * @param cellVSpan Height in cells
+     * @param resultRect Rect into which to put the results
+     */
+    public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
+        final int cellWidth = mCellWidth;
+        final int cellHeight = mCellHeight;
+        final int widthGap = mWidthGap;
+        final int heightGap = mHeightGap;
+
+        final int hStartPadding = getPaddingLeft();
+        final int vStartPadding = getPaddingTop();
+
+        int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
+        int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
+
+        int x = hStartPadding + cellX * (cellWidth + widthGap);
+        int y = vStartPadding + cellY * (cellHeight + heightGap);
+
+        resultRect.set(x, y, x + width, y + height);
+    }
+
+    /**
+     * Computes the required horizontal and vertical cell spans to always
+     * fit the given rectangle.
+     *
+     * @param width Width in pixels
+     * @param height Height in pixels
+     * @param result An array of length 2 in which to store the result (may be null).
+     */
+    public static int[] rectToCell(int width, int height, int[] result) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+
+        // Always assume we're working with the smallest span to make sure we
+        // reserve enough space in both orientations.
+        int parentWidth = grid.calculateCellWidth(grid.widthPx
+                - padding.left - padding.right, (int) grid.numColumns);
+        int parentHeight = grid.calculateCellHeight(grid.heightPx
+                - padding.top - padding.bottom, (int) grid.numRows);
+        int smallerSize = Math.min(parentWidth, parentHeight);
+
+        // Always round up to next largest cell
+        int spanX = (int) Math.ceil(width / (float) smallerSize);
+        int spanY = (int) Math.ceil(height / (float) smallerSize);
+
+        if (result == null) {
+            return new int[] { spanX, spanY };
+        }
+        result[0] = spanX;
+        result[1] = spanY;
+        return result;
+    }
+
+    public int[] cellSpansToSize(int hSpans, int vSpans) {
+        int[] size = new int[2];
+        size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
+        size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
+        return size;
+    }
+
+    /**
+     * Calculate the grid spans needed to fit given item
+     */
+    public void calculateSpans(ItemInfo info) {
+        final int minWidth;
+        final int minHeight;
+
+        if (info instanceof LauncherAppWidgetInfo) {
+            minWidth = ((LauncherAppWidgetInfo) info).minWidth;
+            minHeight = ((LauncherAppWidgetInfo) info).minHeight;
+        } else if (info instanceof PendingAddWidgetInfo) {
+            minWidth = ((PendingAddWidgetInfo) info).minWidth;
+            minHeight = ((PendingAddWidgetInfo) info).minHeight;
+        } else {
+            // It's not a widget, so it must be 1x1
+            info.spanX = info.spanY = 1;
+            return;
+        }
+        int[] spans = rectToCell(minWidth, minHeight, null);
+        info.spanX = spans[0];
+        info.spanY = spans[1];
+    }
+
+    /**
+     * Find the first vacant cell, if there is one.
+     *
+     * @param vacant Holds the x and y coordinate of the vacant cell
+     * @param spanX Horizontal cell span.
+     * @param spanY Vertical cell span.
+     *
+     * @return True if a vacant cell was found
+     */
+    public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
+
+        return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
+    }
+
+    static boolean findVacantCell(int[] vacant, int spanX, int spanY,
+            int xCount, int yCount, boolean[][] occupied) {
+
+        for (int y = 0; y < yCount; y++) {
+            for (int x = 0; x < xCount; x++) {
+                boolean available = !occupied[x][y];
+out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
+                    for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
+                        available = available && !occupied[i][j];
+                        if (!available) break out;
+                    }
+                }
+
+                if (available) {
+                    vacant[0] = x;
+                    vacant[1] = y;
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void clearOccupiedCells() {
+        for (int x = 0; x < mCountX; x++) {
+            for (int y = 0; y < mCountY; y++) {
+                mOccupied[x][y] = false;
+            }
+        }
+    }
+
+    public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
+        markCellsAsUnoccupiedForView(view);
+        markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
+    }
+
+    public void markCellsAsOccupiedForView(View view) {
+        markCellsAsOccupiedForView(view, mOccupied);
+    }
+    public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
+        if (view == null || view.getParent() != mShortcutsAndWidgets) return;
+        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
+    }
+
+    public void markCellsAsUnoccupiedForView(View view) {
+        markCellsAsUnoccupiedForView(view, mOccupied);
+    }
+    public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
+        if (view == null || view.getParent() != mShortcutsAndWidgets) return;
+        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
+    }
+
+    private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
+            boolean value) {
+        if (cellX < 0 || cellY < 0) return;
+        for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
+            for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
+                occupied[x][y] = value;
+            }
+        }
+    }
+
+    public int getDesiredWidth() {
+        return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
+                (Math.max((mCountX - 1), 0) * mWidthGap);
+    }
+
+    public int getDesiredHeight()  {
+        return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
+                (Math.max((mCountY - 1), 0) * mHeightGap);
+    }
+
+    public boolean isOccupied(int x, int y) {
+        if (x < mCountX && y < mCountY) {
+            return mOccupied[x][y];
+        } else {
+            throw new RuntimeException("Position exceeds the bound of this CellLayout");
+        }
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new CellLayout.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof CellLayout.LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new CellLayout.LayoutParams(p);
+    }
+
+    public static class CellLayoutAnimationController extends LayoutAnimationController {
+        public CellLayoutAnimationController(Animation animation, float delay) {
+            super(animation, delay);
+        }
+
+        @Override
+        protected long getDelayForView(View view) {
+            return (int) (Math.random() * 150);
+        }
+    }
+
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        /**
+         * Horizontal location of the item in the grid.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellX;
+
+        /**
+         * Vertical location of the item in the grid.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellY;
+
+        /**
+         * Temporary horizontal location of the item in the grid during reorder
+         */
+        public int tmpCellX;
+
+        /**
+         * Temporary vertical location of the item in the grid during reorder
+         */
+        public int tmpCellY;
+
+        /**
+         * Indicates that the temporary coordinates should be used to layout the items
+         */
+        public boolean useTmpCoords;
+
+        /**
+         * Number of cells spanned horizontally by the item.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellHSpan;
+
+        /**
+         * Number of cells spanned vertically by the item.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellVSpan;
+
+        /**
+         * Indicates whether the item will set its x, y, width and height parameters freely,
+         * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
+         */
+        public boolean isLockedToGrid = true;
+
+        /**
+         * Indicates that this item should use the full extents of its parent.
+         */
+        public boolean isFullscreen = false;
+
+        /**
+         * Indicates whether this item can be reordered. Always true except in the case of the
+         * the AllApps button.
+         */
+        public boolean canReorder = true;
+
+        // X coordinate of the view in the layout.
+        @ViewDebug.ExportedProperty
+        int x;
+        // Y coordinate of the view in the layout.
+        @ViewDebug.ExportedProperty
+        int y;
+
+        boolean dropped;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+            this.cellX = source.cellX;
+            this.cellY = source.cellY;
+            this.cellHSpan = source.cellHSpan;
+            this.cellVSpan = source.cellVSpan;
+        }
+
+        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            this.cellX = cellX;
+            this.cellY = cellY;
+            this.cellHSpan = cellHSpan;
+            this.cellVSpan = cellVSpan;
+        }
+
+        public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
+                boolean invertHorizontally, int colCount) {
+            if (isLockedToGrid) {
+                final int myCellHSpan = cellHSpan;
+                final int myCellVSpan = cellVSpan;
+                int myCellX = useTmpCoords ? tmpCellX : cellX;
+                int myCellY = useTmpCoords ? tmpCellY : cellY;
+
+                if (invertHorizontally) {
+                    myCellX = colCount - myCellX - cellHSpan;
+                }
+
+                width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+                        leftMargin - rightMargin;
+                height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+                        topMargin - bottomMargin;
+                x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
+                y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
+            }
+        }
+
+        public String toString() {
+            return "(" + this.cellX + ", " + this.cellY + ")";
+        }
+
+        public void setWidth(int width) {
+            this.width = width;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public void setHeight(int height) {
+            this.height = height;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        public void setX(int x) {
+            this.x = x;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public void setY(int y) {
+            this.y = y;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    // This class stores info for two purposes:
+    // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
+    //    its spanX, spanY, and the screen it is on
+    // 2. When long clicking on an empty cell in a CellLayout, we save information about the
+    //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
+    //    the CellLayout that was long clicked
+    static final class CellInfo {
+        View cell;
+        int cellX = -1;
+        int cellY = -1;
+        int spanX;
+        int spanY;
+        long screenId;
+        long container;
+
+        @Override
+        public String toString() {
+            return "Cell[view=" + (cell == null ? "null" : cell.getClass())
+                    + ", x=" + cellX + ", y=" + cellY + "]";
+        }
+    }
+
+    public boolean lastDownOnOccupiedCell() {
+        return mLastDownOnOccupiedCell;
+    }
+}
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
new file mode 100644
index 0000000..8114979
--- /dev/null
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.launcher3;
+
+import android.view.View;
+
+public class CheckLongPressHelper {
+    private View mView;
+    private boolean mHasPerformedLongPress;
+    private CheckForLongPress mPendingCheckForLongPress;
+
+    class CheckForLongPress implements Runnable {
+        public void run() {
+            if ((mView.getParent() != null) && mView.hasWindowFocus()
+                    && !mHasPerformedLongPress) {
+                if (mView.performLongClick()) {
+                    mView.setPressed(false);
+                    mHasPerformedLongPress = true;
+                }
+            }
+        }
+    }
+
+    public CheckLongPressHelper(View v) {
+        mView = v;
+    }
+
+    public void postCheckForLongPress() {
+        mHasPerformedLongPress = false;
+
+        if (mPendingCheckForLongPress == null) {
+            mPendingCheckForLongPress = new CheckForLongPress();
+        }
+        mView.postDelayed(mPendingCheckForLongPress,
+                LauncherAppState.getInstance().getLongPressTimeout());
+    }
+
+    public void cancelLongPress() {
+        mHasPerformedLongPress = false;
+        if (mPendingCheckForLongPress != null) {
+            mView.removeCallbacks(mPendingCheckForLongPress);
+            mPendingCheckForLongPress = null;
+        }
+    }
+
+    public boolean hasPerformedLongPress() {
+        return mHasPerformedLongPress;
+    }
+}
diff --git a/src/com/android/launcher3/CheckableFrameLayout.java b/src/com/android/launcher3/CheckableFrameLayout.java
new file mode 100644
index 0000000..5b7d824
--- /dev/null
+++ b/src/com/android/launcher3/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.launcher3;
+
+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/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
new file mode 100644
index 0000000..338b722
--- /dev/null
+++ b/src/com/android/launcher3/Cling.java
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+public class Cling extends FrameLayout implements Insettable, View.OnClickListener,
+        View.OnLongClickListener, View.OnTouchListener {
+
+    static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
+    static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
+    static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
+
+    private static String FIRST_RUN_PORTRAIT = "first_run_portrait";
+    private static String FIRST_RUN_LANDSCAPE = "first_run_landscape";
+
+    private static String WORKSPACE_PORTRAIT = "workspace_portrait";
+    private static String WORKSPACE_LANDSCAPE = "workspace_landscape";
+    private static String WORKSPACE_LARGE = "workspace_large";
+    private static String WORKSPACE_CUSTOM = "workspace_custom";
+
+    private static String FOLDER_PORTRAIT = "folder_portrait";
+    private static String FOLDER_LANDSCAPE = "folder_landscape";
+    private static String FOLDER_LARGE = "folder_large";
+
+    private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
+    private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
+    private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
+    private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
+
+    private Launcher mLauncher;
+    private boolean mIsInitialized;
+    private String mDrawIdentifier;
+    private Drawable mBackground;
+
+    private int[] mTouchDownPt = new int[2];
+
+    private Drawable mFocusedHotseatApp;
+    private ComponentName mFocusedHotseatAppComponent;
+    private Rect mFocusedHotseatAppBounds;
+
+    private Paint mErasePaint;
+    private Paint mBubblePaint;
+    private Paint mDotPaint;
+
+    private View mScrimView;
+    private int mBackgroundColor;
+
+    private final Rect mInsets = new Rect();
+
+    public Cling(Context context) {
+        this(context, null, 0);
+    }
+
+    public Cling(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public Cling(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0);
+        mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier);
+        a.recycle();
+
+        setClickable(true);
+
+    }
+
+    void init(Launcher l, View scrim) {
+        if (!mIsInitialized) {
+            mLauncher = l;
+            mScrimView = scrim;
+            mBackgroundColor = 0xdd000000;
+            setOnLongClickListener(this);
+            setOnClickListener(this);
+            setOnTouchListener(this);
+
+            mErasePaint = new Paint();
+            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+            mErasePaint.setColor(0xFFFFFF);
+            mErasePaint.setAlpha(0);
+            mErasePaint.setAntiAlias(true);
+
+            int circleColor = getResources().getColor(
+                    R.color.first_run_cling_circle_background_color);
+            mBubblePaint = new Paint();
+            mBubblePaint.setColor(circleColor);
+            mBubblePaint.setAntiAlias(true);
+
+            mDotPaint = new Paint();
+            mDotPaint.setColor(0x72BBED);
+            mDotPaint.setAntiAlias(true);
+
+            mIsInitialized = true;
+        }
+    }
+
+    void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title,
+                              String description) {
+        // Get the app to draw
+        Resources r = getResources();
+        int appIconId = drawableId;
+        Hotseat hotseat = mLauncher.getHotseat();
+        if (hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
+                !description.isEmpty()) {
+            // Set the app bounds
+            int x = hotseat.getCellXFromOrder(appRank);
+            int y = hotseat.getCellYFromOrder(appRank);
+            Rect pos = hotseat.getCellCoordinates(x, y);
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            mFocusedHotseatApp = getResources().getDrawable(appIconId);
+            mFocusedHotseatAppComponent = cn;
+            mFocusedHotseatAppBounds = new Rect(pos.left, pos.top,
+                    pos.left + Utilities.sIconTextureWidth,
+                    pos.top + Utilities.sIconTextureHeight);
+            Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
+                    (grid.hotseatIconSize / grid.iconSize));
+
+            // Set the title
+            TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
+            if (v != null) {
+                v.setText(title);
+            }
+
+            // Set the description
+            v = (TextView) findViewById(R.id.focused_hotseat_app_description);
+            if (v != null) {
+                v.setText(description);
+            }
+
+            // Show the bubble
+            View bubble = findViewById(R.id.focused_hotseat_app_bubble);
+            bubble.setVisibility(View.VISIBLE);
+        }
+    }
+
+    void show(boolean animate, int duration) {
+        setVisibility(View.VISIBLE);
+        setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+                mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+                mDrawIdentifier.equals(WORKSPACE_LARGE) ||
+                mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
+            View content = getContent();
+            content.setAlpha(0f);
+            content.animate()
+                    .alpha(1f)
+                    .setDuration(duration)
+                    .setListener(null)
+                    .start();
+            setAlpha(1f);
+        } else {
+            if (animate) {
+                buildLayer();
+                setAlpha(0f);
+                animate()
+                    .alpha(1f)
+                    .setInterpolator(new AccelerateInterpolator())
+                    .setDuration(duration)
+                    .setListener(null)
+                    .start();
+            } else {
+                setAlpha(1f);
+            }
+        }
+
+        // Show the scrim if necessary
+        if (mScrimView != null) {
+            mScrimView.setVisibility(View.VISIBLE);
+            mScrimView.setAlpha(0f);
+            mScrimView.animate()
+                    .alpha(1f)
+                    .setDuration(duration)
+                    .setListener(null)
+                    .start();
+        }
+
+        setFocusableInTouchMode(true);
+        post(new Runnable() {
+            public void run() {
+                setFocusable(true);
+                requestFocus();
+            }
+        });
+    }
+
+    void hide(final int duration, final Runnable postCb) {
+        if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
+                mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
+            View content = getContent();
+            content.animate()
+                .alpha(0f)
+                .setDuration(duration)
+                .setListener(new AnimatorListenerAdapter() {
+                    public void onAnimationEnd(Animator animation) {
+                        // We are about to trigger the workspace cling, so don't do anything else
+                        setVisibility(View.GONE);
+                        postCb.run();
+                    };
+                })
+                .start();
+        } else {
+            animate()
+                .alpha(0f)
+                .setDuration(duration)
+                .setListener(new AnimatorListenerAdapter() {
+                    public void onAnimationEnd(Animator animation) {
+                        // We are about to trigger the workspace cling, so don't do anything else
+                        setVisibility(View.GONE);
+                        postCb.run();
+                    };
+                })
+                .start();
+        }
+
+        // Show the scrim if necessary
+        if (mScrimView != null) {
+            mScrimView.animate()
+                .alpha(0f)
+                .setDuration(duration)
+                .setListener(new AnimatorListenerAdapter() {
+                    public void onAnimationEnd(Animator animation) {
+                        mScrimView.setVisibility(View.GONE);
+                    };
+                })
+                .start();
+        }
+    }
+
+    void cleanup() {
+        mBackground = null;
+        mIsInitialized = false;
+    }
+
+    void bringScrimToFront() {
+        if (mScrimView != null) {
+            mScrimView.bringToFront();
+        }
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        setPadding(insets.left, insets.top, insets.right, insets.bottom);
+    }
+
+    View getContent() {
+        return findViewById(R.id.content);
+    }
+
+    String getDrawIdentifier() {
+        return mDrawIdentifier;
+    }
+
+    @Override
+    public View focusSearch(int direction) {
+        return this.focusSearch(this, direction);
+    }
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        return FocusFinder.getInstance().findNextFocus(this, focused, direction);
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)
+                || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)
+                || mDrawIdentifier.equals(WORKSPACE_LARGE)
+                || mDrawIdentifier.equals(WORKSPACE_CUSTOM));
+    }
+
+    @Override
+    public boolean onTouchEvent(android.view.MotionEvent event) {
+        if (mDrawIdentifier.equals(FOLDER_PORTRAIT) ||
+                   mDrawIdentifier.equals(FOLDER_LANDSCAPE) ||
+                   mDrawIdentifier.equals(FOLDER_LARGE)) {
+            Folder f = mLauncher.getWorkspace().getOpenFolder();
+            if (f != null) {
+                Rect r = new Rect();
+                f.getHitRect(r);
+                if (r.contains((int) event.getX(), (int) event.getY())) {
+                    return false;
+                }
+            }
+        }
+        return super.onTouchEvent(event);
+    };
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchDownPt[0] = (int) ev.getX();
+            mTouchDownPt[1] = (int) ev.getY();
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+                mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+                mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+            if (mFocusedHotseatAppBounds != null &&
+                mFocusedHotseatAppBounds.contains(mTouchDownPt[0], mTouchDownPt[1])) {
+                // Launch the activity that is being highlighted
+                Intent intent = new Intent(Intent.ACTION_MAIN);
+                intent.setComponent(mFocusedHotseatAppComponent);
+                intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                mLauncher.startActivity(intent, null);
+                mLauncher.dismissWorkspaceCling(this);
+            }
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+                mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+                mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+            mLauncher.dismissWorkspaceCling(null);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mIsInitialized) {
+            canvas.save();
+
+            // Get the background override if there is one
+            if (mBackground == null) {
+                if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
+                    mBackground = getResources().getDrawable(R.drawable.bg_cling5);
+                }
+            }
+            // Draw the background
+            Bitmap eraseBg = null;
+            Canvas eraseCanvas = null;
+            if (mScrimView != null) {
+                // Skip drawing the background
+                mScrimView.setBackgroundColor(mBackgroundColor);
+            } else if (mBackground != null) {
+                mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
+                mBackground.draw(canvas);
+            } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+                    mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+                    mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+                // Initialize the draw buffer (to allow punching through)
+                eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+                        Bitmap.Config.ARGB_8888);
+                eraseCanvas = new Canvas(eraseBg);
+                eraseCanvas.drawColor(mBackgroundColor);
+            } else {
+                canvas.drawColor(mBackgroundColor);
+            }
+
+            // Draw everything else
+            DisplayMetrics metrics = new DisplayMetrics();
+            mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+            float alpha = getAlpha();
+            View content = getContent();
+            if (content != null) {
+                alpha *= content.getAlpha();
+            }
+            if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
+                    mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
+                // Draw the circle
+                View bubbleContent = findViewById(R.id.bubble_content);
+                Rect bubbleRect = new Rect();
+                bubbleContent.getGlobalVisibleRect(bubbleRect);
+                mBubblePaint.setAlpha((int) (255 * alpha));
+                float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics);
+                canvas.drawCircle(metrics.widthPixels / 2,
+                        bubbleRect.centerY(),
+                        (bubbleContent.getMeasuredWidth() + buffer) / 2,
+                        mBubblePaint);
+            } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+                    mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+                    mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+                int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
+                mErasePaint.setAlpha((int) (128));
+                eraseCanvas.drawCircle(metrics.widthPixels / 2,
+                        metrics.heightPixels / 2 - offset,
+                        DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
+                        mErasePaint);
+                mErasePaint.setAlpha(0);
+                eraseCanvas.drawCircle(metrics.widthPixels / 2,
+                        metrics.heightPixels / 2 - offset,
+                        DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
+                        mErasePaint);
+                canvas.drawBitmap(eraseBg, 0, 0, null);
+                eraseCanvas.setBitmap(null);
+                eraseBg = null;
+
+                // Draw the focused hotseat app icon
+                if (mFocusedHotseatAppBounds != null && mFocusedHotseatApp != null) {
+                    mFocusedHotseatApp.setBounds(mFocusedHotseatAppBounds.left,
+                            mFocusedHotseatAppBounds.top, mFocusedHotseatAppBounds.right,
+                            mFocusedHotseatAppBounds.bottom);
+                    mFocusedHotseatApp.setAlpha((int) (255 * alpha));
+                    mFocusedHotseatApp.draw(canvas);
+                }
+            }
+
+            canvas.restore();
+        }
+
+        // Draw the rest of the cling
+        super.dispatchDraw(canvas);
+    };
+}
diff --git a/src/com/android/launcher3/CropView.java b/src/com/android/launcher3/CropView.java
new file mode 100644
index 0000000..9224e3b
--- /dev/null
+++ b/src/com/android/launcher3/CropView.java
@@ -0,0 +1,321 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+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, 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) {
+    }
+
+    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] = FloatMath.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] = FloatMath.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/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
new file mode 100644
index 0000000..92ecf96
--- /dev/null
+++ b/src/com/android/launcher3/DeferredHandler.java
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.util.Pair;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * Queue of things to run on a looper thread.  Items posted with {@link #post} will not
+ * be actually enqued on the handler until after the last one has run, to keep from
+ * starving the thread.
+ *
+ * This class is fifo.
+ */
+public class DeferredHandler {
+    private LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
+    private MessageQueue mMessageQueue = Looper.myQueue();
+    private Impl mHandler = new Impl();
+
+    private class Impl extends Handler implements MessageQueue.IdleHandler {
+        public void handleMessage(Message msg) {
+            Pair<Runnable, Integer> p;
+            Runnable r;
+            synchronized (mQueue) {
+                if (mQueue.size() == 0) {
+                    return;
+                }
+                p = mQueue.removeFirst();
+                r = p.first;
+            }
+            r.run();
+            synchronized (mQueue) {
+                scheduleNextLocked();
+            }
+        }
+
+        public boolean queueIdle() {
+            handleMessage(null);
+            return false;
+        }
+    }
+
+    private class IdleRunnable implements Runnable {
+        Runnable mRunnable;
+
+        IdleRunnable(Runnable r) {
+            mRunnable = r;
+        }
+
+        public void run() {
+            mRunnable.run();
+        }
+    }
+
+    public DeferredHandler() {
+    }
+
+    /** Schedule runnable to run after everything that's on the queue right now. */
+    public void post(Runnable runnable) {
+        post(runnable, 0);
+    }
+    public void post(Runnable runnable, int type) {
+        synchronized (mQueue) {
+            mQueue.add(new Pair<Runnable, Integer>(runnable, type));
+            if (mQueue.size() == 1) {
+                scheduleNextLocked();
+            }
+        }
+    }
+
+    /** Schedule runnable to run when the queue goes idle. */
+    public void postIdle(final Runnable runnable) {
+        postIdle(runnable, 0);
+    }
+    public void postIdle(final Runnable runnable, int type) {
+        post(new IdleRunnable(runnable), type);
+    }
+
+    public void cancelRunnable(Runnable runnable) {
+        synchronized (mQueue) {
+            while (mQueue.remove(runnable)) { }
+        }
+    }
+    public void cancelAllRunnablesOfType(int type) {
+        synchronized (mQueue) {
+            ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator();
+            Pair<Runnable, Integer> p;
+            while (iter.hasNext()) {
+                p = iter.next();
+                if (p.second == type) {
+                    iter.remove();
+                }
+            }
+        }
+    }
+
+    public void cancel() {
+        synchronized (mQueue) {
+            mQueue.clear();
+        }
+    }
+
+    /** Runs all queued Runnables from the calling thread. */
+    public void flush() {
+        LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>();
+        synchronized (mQueue) {
+            queue.addAll(mQueue);
+            mQueue.clear();
+        }
+        for (Pair<Runnable, Integer> p : queue) {
+            p.first.run();
+        }
+    }
+
+    void scheduleNextLocked() {
+        if (mQueue.size() > 0) {
+            Pair<Runnable, Integer> p = mQueue.getFirst();
+            Runnable peek = p.first;
+            if (peek instanceof IdleRunnable) {
+                mMessageQueue.addIdleHandler(mHandler);
+            } else {
+                mHandler.sendEmptyMessage(1);
+            }
+        }
+    }
+}
+
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
new file mode 100644
index 0000000..4023daf
--- /dev/null
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -0,0 +1,540 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.List;
+import java.util.Set;
+
+public class DeleteDropTarget extends ButtonDropTarget {
+    private static int DELETE_ANIMATION_DURATION = 285;
+    private static int FLING_DELETE_ANIMATION_DURATION = 350;
+    private static float FLING_TO_DELETE_FRICTION = 0.035f;
+    private static int MODE_FLING_DELETE_TO_TRASH = 0;
+    private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
+
+    private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
+
+    private ColorStateList mOriginalTextColor;
+    private TransitionDrawable mUninstallDrawable;
+    private TransitionDrawable mRemoveDrawable;
+    private TransitionDrawable mCurrentDrawable;
+
+    private boolean mWaitingForUninstall = false;
+
+    public DeleteDropTarget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Get the drawable
+        mOriginalTextColor = getTextColors();
+
+        // Get the hover color
+        Resources r = getResources();
+        mHoverColor = r.getColor(R.color.delete_target_hover_tint);
+        mUninstallDrawable = (TransitionDrawable) 
+                r.getDrawable(R.drawable.uninstall_target_selector);
+        mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector);
+
+        mRemoveDrawable.setCrossFadeEnabled(true);
+        mUninstallDrawable.setCrossFadeEnabled(true);
+
+        // The current drawable is set to either the remove drawable or the uninstall drawable 
+        // and is initially set to the remove drawable, as set in the layout xml.
+        mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
+
+        // Remove the text in the Phone UI in landscape
+        int orientation = getResources().getConfiguration().orientation;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            if (!LauncherAppState.getInstance().isScreenLarge()) {
+                setText("");
+            }
+        }
+    }
+
+    private boolean isAllAppsApplication(DragSource source, Object info) {
+        return (source instanceof AppsCustomizePagedView) && (info instanceof AppInfo);
+    }
+    private boolean isAllAppsWidget(DragSource source, Object info) {
+        if (source instanceof AppsCustomizePagedView) {
+            if (info instanceof PendingAddItemInfo) {
+                PendingAddItemInfo addInfo = (PendingAddItemInfo) info;
+                switch (addInfo.itemType) {
+                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                        return true;
+                }
+            }
+        }
+        return false;
+    }
+    private boolean isDragSourceWorkspaceOrFolder(DragObject d) {
+        return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder);
+    }
+    private boolean isWorkspaceOrFolderApplication(DragObject d) {
+        return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof ShortcutInfo);
+    }
+    private boolean isWorkspaceOrFolderWidget(DragObject d) {
+        return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof LauncherAppWidgetInfo);
+    }
+    private boolean isWorkspaceFolder(DragObject d) {
+        return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo);
+    }
+
+    private void setHoverColor() {
+        mCurrentDrawable.startTransition(mTransitionDuration);
+        setTextColor(mHoverColor);
+    }
+    private void resetHoverColor() {
+        mCurrentDrawable.resetTransition();
+        setTextColor(mOriginalTextColor);
+    }
+
+    @Override
+    public boolean acceptDrop(DragObject d) {
+        return willAcceptDrop(d.dragInfo);
+    }
+
+    public static boolean willAcceptDrop(Object info) {
+        if (info instanceof ItemInfo) {
+            ItemInfo item = (ItemInfo) info;
+            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
+                    item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                return true;
+            }
+
+            if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                    item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                return true;
+            }
+
+            if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                    item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+                    item instanceof AppInfo) {
+                AppInfo appInfo = (AppInfo) info;
+                return (appInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
+            }
+
+            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+                item instanceof ShortcutInfo) {
+                if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+                    ShortcutInfo shortcutInfo = (ShortcutInfo) info;
+                    return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
+                } else {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onDragStart(DragSource source, Object info, int dragAction) {
+        boolean isVisible = true;
+        boolean useUninstallLabel = !AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                isAllAppsApplication(source, info);
+
+        // If we are dragging an application from AppsCustomize, only show the control if we can
+        // delete the app (it was downloaded), and rename the string to "uninstall" in such a case.
+        // Hide the delete target if it is a widget from AppsCustomize.
+        if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
+            isVisible = false;
+        }
+
+        if (useUninstallLabel) {
+            setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
+        } else {
+            setCompoundDrawablesRelativeWithIntrinsicBounds(mRemoveDrawable, null, null, null);
+        }
+        mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
+
+        mActive = isVisible;
+        resetHoverColor();
+        ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
+        if (getText().length() > 0) {
+            setText(useUninstallLabel ? R.string.delete_target_uninstall_label
+                : R.string.delete_target_label);
+        }
+    }
+
+    @Override
+    public void onDragEnd() {
+        super.onDragEnd();
+        mActive = false;
+    }
+
+    public void onDragEnter(DragObject d) {
+        super.onDragEnter(d);
+
+        setHoverColor();
+    }
+
+    public void onDragExit(DragObject d) {
+        super.onDragExit(d);
+
+        if (!d.dragComplete) {
+            resetHoverColor();
+        } else {
+            // Restore the hover color if we are deleting
+            d.dragView.setColor(mHoverColor);
+        }
+    }
+
+    private void animateToTrashAndCompleteDrop(final DragObject d) {
+        final DragLayer dragLayer = mLauncher.getDragLayer();
+        final Rect from = new Rect();
+        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+                mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+        final float scale = (float) to.width() / from.width();
+
+        mSearchDropTargetBar.deferOnDragEnd();
+        deferCompleteDropIfUninstalling(d);
+
+        Runnable onAnimationEndRunnable = new Runnable() {
+            @Override
+            public void run() {
+                completeDrop(d);
+                mSearchDropTargetBar.onDragEnd();
+                mLauncher.exitSpringLoadedDragMode();
+            }
+        };
+        dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
+                DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2),
+                new LinearInterpolator(), onAnimationEndRunnable,
+                DragLayer.ANIMATION_END_DISAPPEAR, null);
+    }
+
+    private void deferCompleteDropIfUninstalling(DragObject d) {
+        mWaitingForUninstall = false;
+        if (isUninstallFromWorkspace(d)) {
+            if (d.dragSource instanceof Folder) {
+                ((Folder) d.dragSource).deferCompleteDropAfterUninstallActivity();
+            } else if (d.dragSource instanceof Workspace) {
+                ((Workspace) d.dragSource).deferCompleteDropAfterUninstallActivity();
+            }
+            mWaitingForUninstall = true;
+        }
+    }
+
+    private boolean isUninstallFromWorkspace(DragObject d) {
+        if (AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d)) {
+            ShortcutInfo shortcut = (ShortcutInfo) d.dragInfo;
+            if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+                Set<String> categories = shortcut.intent.getCategories();
+                boolean includesLauncherCategory = false;
+                if (categories != null) {
+                    for (String category : categories) {
+                        if (category.equals(Intent.CATEGORY_LAUNCHER)) {
+                            includesLauncherCategory = true;
+                            break;
+                        }
+                    }
+                }
+                return includesLauncherCategory;
+            }
+        }
+        return false;
+    }
+
+    private void completeDrop(DragObject d) {
+        ItemInfo item = (ItemInfo) d.dragInfo;
+        boolean wasWaitingForUninstall = mWaitingForUninstall;
+        mWaitingForUninstall = false;
+        if (isAllAppsApplication(d.dragSource, item)) {
+            // Uninstall the application if it is being dragged from AppsCustomize
+            AppInfo appInfo = (AppInfo) item;
+            mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+        } else if (isUninstallFromWorkspace(d)) {
+            ShortcutInfo shortcut = (ShortcutInfo) item;
+            if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+                final ComponentName componentName = shortcut.intent.getComponent();
+                final DragSource dragSource = d.dragSource;
+                int flags = AppInfo.initFlags(
+                    ShortcutInfo.getPackageInfo(getContext(), componentName.getPackageName()));
+                mWaitingForUninstall =
+                    mLauncher.startApplicationUninstallActivity(componentName, flags);
+                if (mWaitingForUninstall) {
+                    final Runnable checkIfUninstallWasSuccess = new Runnable() {
+                        @Override
+                        public void run() {
+                            mWaitingForUninstall = false;
+                            String packageName = componentName.getPackageName();
+                            List<ResolveInfo> activities =
+                                    AllAppsList.findActivitiesForPackage(getContext(), packageName);
+                            boolean uninstallSuccessful = activities.size() == 0;
+                            if (dragSource instanceof Folder) {
+                                ((Folder) dragSource).
+                                    onUninstallActivityReturned(uninstallSuccessful);
+                            } else if (dragSource instanceof Workspace) {
+                                ((Workspace) dragSource).
+                                    onUninstallActivityReturned(uninstallSuccessful);
+                            }
+                        }
+                    };
+                    mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
+                }
+            }
+        } else if (isWorkspaceOrFolderApplication(d)) {
+            LauncherModel.deleteItemFromDatabase(mLauncher, item);
+        } else if (isWorkspaceFolder(d)) {
+            // Remove the folder from the workspace and delete the contents from launcher model
+            FolderInfo folderInfo = (FolderInfo) item;
+            mLauncher.removeFolder(folderInfo);
+            LauncherModel.deleteFolderContentsFromDatabase(mLauncher, folderInfo);
+        } else if (isWorkspaceOrFolderWidget(d)) {
+            // Remove the widget from the workspace
+            mLauncher.removeAppWidget((LauncherAppWidgetInfo) item);
+            LauncherModel.deleteItemFromDatabase(mLauncher, item);
+
+            final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item;
+            final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
+            if (appWidgetHost != null) {
+                // Deleting an app widget ID is a void call but writes to disk before returning
+                // to the caller...
+                new Thread("deleteAppWidgetId") {
+                    public void run() {
+                        appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
+                    }
+                }.start();
+            }
+        }
+        if (wasWaitingForUninstall && !mWaitingForUninstall) {
+            if (d.dragSource instanceof Folder) {
+                ((Folder) d.dragSource).onUninstallActivityReturned(false);
+            } else if (d.dragSource instanceof Workspace) {
+                ((Workspace) d.dragSource).onUninstallActivityReturned(false);
+            }
+        }
+    }
+
+    public void onDrop(DragObject d) {
+        animateToTrashAndCompleteDrop(d);
+    }
+
+    /**
+     * Creates an animation from the current drag view to the delete trash icon.
+     */
+    private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
+            DragObject d, PointF vel, ViewConfiguration config) {
+        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+                mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+        final Rect from = new Rect();
+        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+        // Calculate how far along the velocity vector we should put the intermediate point on
+        // the bezier curve
+        float velocity = Math.abs(vel.length());
+        float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
+        int offsetY = (int) (-from.top * vp);
+        int offsetX = (int) (offsetY / (vel.y / vel.x));
+        final float y2 = from.top + offsetY;                        // intermediate t/l
+        final float x2 = from.left + offsetX;
+        final float x1 = from.left;                                 // drag view t/l
+        final float y1 = from.top;
+        final float x3 = to.left;                                   // delete target t/l
+        final float y3 = to.top;
+
+        final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
+            @Override
+            public float getInterpolation(float t) {
+                return t * t * t * t * t * t * t * t;
+            }
+        };
+        return new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final DragView dragView = (DragView) dragLayer.getAnimatedView();
+                float t = ((Float) animation.getAnimatedValue()).floatValue();
+                float tp = scaleAlphaInterpolator.getInterpolation(t);
+                float initialScale = dragView.getInitialScale();
+                float finalAlpha = 0.5f;
+                float scale = dragView.getScaleX();
+                float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
+                float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
+                float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
+                        (t * t) * x3;
+                float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
+                        (t * t) * y3;
+
+                dragView.setTranslationX(x);
+                dragView.setTranslationY(y);
+                dragView.setScaleX(initialScale * (1f - tp));
+                dragView.setScaleY(initialScale * (1f - tp));
+                dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
+            }
+        };
+    }
+
+    /**
+     * Creates an animation from the current drag view along its current velocity vector.
+     * For this animation, the alpha runs for a fixed duration and we update the position
+     * progressively.
+     */
+    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+        private DragLayer mDragLayer;
+        private PointF mVelocity;
+        private Rect mFrom;
+        private long mPrevTime;
+        private boolean mHasOffsetForScale;
+        private float mFriction;
+
+        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+        public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
+                long startTime, float friction) {
+            mDragLayer = dragLayer;
+            mVelocity = vel;
+            mFrom = from;
+            mPrevTime = startTime;
+            mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            final DragView dragView = (DragView) mDragLayer.getAnimatedView();
+            float t = ((Float) animation.getAnimatedValue()).floatValue();
+            long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+            if (!mHasOffsetForScale) {
+                mHasOffsetForScale = true;
+                float scale = dragView.getScaleX();
+                float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
+                float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
+
+                mFrom.left += xOffset;
+                mFrom.top += yOffset;
+            }
+
+            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+            dragView.setTranslationX(mFrom.left);
+            dragView.setTranslationY(mFrom.top);
+            dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+            mVelocity.x *= mFriction;
+            mVelocity.y *= mFriction;
+            mPrevTime = curTime;
+        }
+    };
+    private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
+            DragObject d, PointF vel, final long startTime, final int duration,
+            ViewConfiguration config) {
+        final Rect from = new Rect();
+        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+        return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
+                FLING_TO_DELETE_FRICTION);
+    }
+
+    public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
+        final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
+
+        // Don't highlight the icon as it's animating
+        d.dragView.setColor(0);
+        d.dragView.updateInitialScaleToCurrentScale();
+        // Don't highlight the target if we are flinging from AllApps
+        if (isAllApps) {
+            resetHoverColor();
+        }
+
+        if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+            // Defer animating out the drop target if we are animating to it
+            mSearchDropTargetBar.deferOnDragEnd();
+            mSearchDropTargetBar.finishAnimations();
+        }
+
+        final ViewConfiguration config = ViewConfiguration.get(mLauncher);
+        final DragLayer dragLayer = mLauncher.getDragLayer();
+        final int duration = FLING_DELETE_ANIMATION_DURATION;
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+        // NOTE: Because it takes time for the first frame of animation to actually be
+        // called and we expect the animation to be a continuation of the fling, we have
+        // to account for the time that has elapsed since the fling finished.  And since
+        // we don't have a startDelay, we will always get call to update when we call
+        // start() (which we want to ignore).
+        final TimeInterpolator tInterpolator = new TimeInterpolator() {
+            private int mCount = -1;
+            private float mOffset = 0f;
+
+            @Override
+            public float getInterpolation(float t) {
+                if (mCount < 0) {
+                    mCount++;
+                } else if (mCount == 0) {
+                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+                            startTime) / duration);
+                    mCount++;
+                }
+                return Math.min(1f, mOffset + t);
+            }
+        };
+        AnimatorUpdateListener updateCb = null;
+        if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+            updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
+        } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
+            updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
+                    duration, config);
+        }
+        deferCompleteDropIfUninstalling(d);
+
+        Runnable onAnimationEndRunnable = new Runnable() {
+            @Override
+            public void run() {
+                // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
+                // itself, otherwise, complete the drop to initiate the deletion process
+                if (!isAllApps) {
+                    mLauncher.exitSpringLoadedDragMode();
+                    completeDrop(d);
+                }
+                mLauncher.getDragController().onDeferredEndFling(d);
+            }
+        };
+        dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
+                DragLayer.ANIMATION_END_DISAPPEAR, null);
+    }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
new file mode 100644
index 0000000..5b5c35c
--- /dev/null
+++ b/src/com/android/launcher3/DragController.java
@@ -0,0 +1,813 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+
+/**
+ * Class for initiating a drag within a view or across multiple views.
+ */
+public class DragController {
+    private static final String TAG = "Launcher.DragController";
+
+    /** Indicates the drag is a move.  */
+    public static int DRAG_ACTION_MOVE = 0;
+
+    /** Indicates the drag is a copy.  */
+    public static int DRAG_ACTION_COPY = 1;
+
+    private static final int SCROLL_DELAY = 500;
+    private static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+
+    private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+
+    private static final int SCROLL_OUTSIDE_ZONE = 0;
+    private static final int SCROLL_WAITING_IN_ZONE = 1;
+
+    static final int SCROLL_NONE = -1;
+    static final int SCROLL_LEFT = 0;
+    static final int SCROLL_RIGHT = 1;
+
+    private static final float MAX_FLING_DEGREES = 35f;
+
+    private Launcher mLauncher;
+    private Handler mHandler;
+
+    // temporaries to avoid gc thrash
+    private Rect mRectTemp = new Rect();
+    private final int[] mCoordinatesTemp = new int[2];
+
+    /** Whether or not we're dragging. */
+    private boolean mDragging;
+
+    /** X coordinate of the down event. */
+    private int mMotionDownX;
+
+    /** Y coordinate of the down event. */
+    private int mMotionDownY;
+
+    /** the area at the edge of the screen that makes the workspace go left
+     *   or right while you're dragging.
+     */
+    private int mScrollZone;
+
+    private DropTarget.DragObject mDragObject;
+
+    /** Who can receive drop events */
+    private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
+    private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
+    private DropTarget mFlingToDeleteDropTarget;
+
+    /** The window token used as the parent for the DragView. */
+    private IBinder mWindowToken;
+
+    /** The view that will be scrolled when dragging to the left and right edges of the screen. */
+    private View mScrollView;
+
+    private View mMoveTarget;
+
+    private DragScroller mDragScroller;
+    private int mScrollState = SCROLL_OUTSIDE_ZONE;
+    private ScrollRunnable mScrollRunnable = new ScrollRunnable();
+
+    private DropTarget mLastDropTarget;
+
+    private InputMethodManager mInputMethodManager;
+
+    private int mLastTouch[] = new int[2];
+    private long mLastTouchUpTime = -1;
+    private int mDistanceSinceScroll = 0;
+
+    private int mTmpPoint[] = new int[2];
+    private Rect mDragLayerRect = new Rect();
+
+    protected int mFlingToDeleteThresholdVelocity;
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * Interface to receive notifications when a drag starts or stops
+     */
+    interface DragListener {
+        /**
+         * A drag has begun
+         *
+         * @param source An object representing where the drag originated
+         * @param info The data associated with the object that is being dragged
+         * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
+         *        or {@link DragController#DRAG_ACTION_COPY}
+         */
+        void onDragStart(DragSource source, Object info, int dragAction);
+
+        /**
+         * The drag has ended
+         */
+        void onDragEnd();
+    }
+    
+    /**
+     * Used to create a new DragLayer from XML.
+     *
+     * @param context The application's context.
+     */
+    public DragController(Launcher launcher) {
+        Resources r = launcher.getResources();
+        mLauncher = launcher;
+        mHandler = new Handler();
+        mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
+        mVelocityTracker = VelocityTracker.obtain();
+
+        float density = r.getDisplayMetrics().density;
+        mFlingToDeleteThresholdVelocity =
+                (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
+    }
+
+    public boolean dragging() {
+        return mDragging;
+    }
+
+    /**
+     * Starts a drag.
+     *
+     * @param v The view that is being dragged
+     * @param bmp The bitmap that represents the view being dragged
+     * @param source An object representing where the drag originated
+     * @param dragInfo The data associated with the object that is being dragged
+     * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+     *        {@link #DRAG_ACTION_COPY}
+     * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+     *          Makes dragging feel more precise, e.g. you can clip out a transparent border
+     */
+    public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
+            Point extraPadding, float initialDragViewScale) {
+        int[] loc = mCoordinatesTemp;
+        mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
+        int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0;
+        int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0;
+        int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft +
+                (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
+        int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop +
+                (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
+
+        startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
+                null, initialDragViewScale);
+
+        if (dragAction == DRAG_ACTION_MOVE) {
+            v.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Starts a drag.
+     *
+     * @param b The bitmap to display as the drag image.  It will be re-scaled to the
+     *          enlarged size.
+     * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
+     * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
+     * @param source An object representing where the drag originated
+     * @param dragInfo The data associated with the object that is being dragged
+     * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+     *        {@link #DRAG_ACTION_COPY}
+     * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+     *          Makes dragging feel more precise, e.g. you can clip out a transparent border
+     */
+    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
+            float initialDragViewScale) {
+        if (PROFILE_DRAWING_DURING_DRAG) {
+            android.os.Debug.startMethodTracing("Launcher");
+        }
+
+        // Hide soft keyboard, if visible
+        if (mInputMethodManager == null) {
+            mInputMethodManager = (InputMethodManager)
+                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
+        }
+        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+
+        for (DragListener listener : mListeners) {
+            listener.onDragStart(source, dragInfo, dragAction);
+        }
+
+        final int registrationX = mMotionDownX - dragLayerX;
+        final int registrationY = mMotionDownY - dragLayerY;
+
+        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+        mDragging = true;
+
+        mDragObject = new DropTarget.DragObject();
+
+        mDragObject.dragComplete = false;
+        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
+        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+        mDragObject.dragSource = source;
+        mDragObject.dragInfo = dragInfo;
+
+        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
+                registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
+
+        if (dragOffset != null) {
+            dragView.setDragVisualizeOffset(new Point(dragOffset));
+        }
+        if (dragRegion != null) {
+            dragView.setDragRegion(new Rect(dragRegion));
+        }
+
+        mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        dragView.show(mMotionDownX, mMotionDownY);
+        handleMoveEvent(mMotionDownX, mMotionDownY);
+    }
+
+    /**
+     * Draw the view into a bitmap.
+     */
+    Bitmap getViewBitmap(View v) {
+        v.clearFocus();
+        v.setPressed(false);
+
+        boolean willNotCache = v.willNotCacheDrawing();
+        v.setWillNotCacheDrawing(false);
+
+        // Reset the drawing cache background color to fully transparent
+        // for the duration of this operation
+        int color = v.getDrawingCacheBackgroundColor();
+        v.setDrawingCacheBackgroundColor(0);
+        float alpha = v.getAlpha();
+        v.setAlpha(1.0f);
+
+        if (color != 0) {
+            v.destroyDrawingCache();
+        }
+        v.buildDrawingCache();
+        Bitmap cacheBitmap = v.getDrawingCache();
+        if (cacheBitmap == null) {
+            Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
+            return null;
+        }
+
+        Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
+
+        // Restore the view
+        v.destroyDrawingCache();
+        v.setAlpha(alpha);
+        v.setWillNotCacheDrawing(willNotCache);
+        v.setDrawingCacheBackgroundColor(color);
+
+        return bitmap;
+    }
+
+    /**
+     * Call this from a drag source view like this:
+     *
+     * <pre>
+     *  @Override
+     *  public boolean dispatchKeyEvent(KeyEvent event) {
+     *      return mDragController.dispatchKeyEvent(this, event)
+     *              || super.dispatchKeyEvent(event);
+     * </pre>
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mDragging;
+    }
+
+    public boolean isDragging() {
+        return mDragging;
+    }
+
+    /**
+     * Stop dragging without dropping.
+     */
+    public void cancelDrag() {
+        if (mDragging) {
+            if (mLastDropTarget != null) {
+                mLastDropTarget.onDragExit(mDragObject);
+            }
+            mDragObject.deferDragViewCleanupPostAnimation = false;
+            mDragObject.cancelled = true;
+            mDragObject.dragComplete = true;
+            mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+        }
+        endDrag();
+    }
+    public void onAppsRemoved(ArrayList<AppInfo> appInfos, Context context) {
+        // Cancel the current drag if we are removing an app that we are dragging
+        if (mDragObject != null) {
+            Object rawDragInfo = mDragObject.dragInfo;
+            if (rawDragInfo instanceof ShortcutInfo) {
+                ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
+                for (AppInfo info : appInfos) {
+                    // Added null checks to prevent NPE we've seen in the wild
+                    if (dragInfo != null &&
+                        dragInfo.intent != null) {
+                        boolean isSameComponent =
+                                dragInfo.intent.getComponent().equals(info.componentName);
+                        if (isSameComponent) {
+                            cancelDrag();
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void endDrag() {
+        if (mDragging) {
+            mDragging = false;
+            clearScrollRunnable();
+            boolean isDeferred = false;
+            if (mDragObject.dragView != null) {
+                isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
+                if (!isDeferred) {
+                    mDragObject.dragView.remove();
+                }
+                mDragObject.dragView = null;
+            }
+
+            // Only end the drag if we are not deferred
+            if (!isDeferred) {
+                for (DragListener listener : mListeners) {
+                    listener.onDragEnd();
+                }
+            }
+        }
+
+        releaseVelocityTracker();
+    }
+
+    /**
+     * This only gets called as a result of drag view cleanup being deferred in endDrag();
+     */
+    void onDeferredEndDrag(DragView dragView) {
+        dragView.remove();
+
+        if (mDragObject.deferDragViewCleanupPostAnimation) {
+            // If we skipped calling onDragEnd() before, do it now
+            for (DragListener listener : mListeners) {
+                listener.onDragEnd();
+            }
+        }
+    }
+
+    void onDeferredEndFling(DropTarget.DragObject d) {
+        d.dragSource.onFlingToDeleteCompleted();
+    }
+
+    /**
+     * Clamps the position to the drag layer bounds.
+     */
+    private int[] getClampedDragLayerPos(float x, float y) {
+        mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
+        mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
+        mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
+        return mTmpPoint;
+    }
+
+    long getLastGestureUpTime() {
+        if (mDragging) {
+            return System.currentTimeMillis();
+        } else {
+            return mLastTouchUpTime;
+        }
+    }
+
+    void resetLastGestureUpTime() {
+        mLastTouchUpTime = -1;
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        @SuppressWarnings("all") // suppress dead code warning
+        final boolean debug = false;
+        if (debug) {
+            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
+                    + mDragging);
+        }
+
+        // Update the velocity tracker
+        acquireVelocityTrackerAndAddMovement(ev);
+
+        final int action = ev.getAction();
+        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+        final int dragLayerX = dragLayerPos[0];
+        final int dragLayerY = dragLayerPos[1];
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                break;
+            case MotionEvent.ACTION_DOWN:
+                // Remember location of down touch
+                mMotionDownX = dragLayerX;
+                mMotionDownY = dragLayerY;
+                mLastDropTarget = null;
+                break;
+            case MotionEvent.ACTION_UP:
+                mLastTouchUpTime = System.currentTimeMillis();
+                if (mDragging) {
+                    PointF vec = isFlingingToDelete(mDragObject.dragSource);
+                    if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+                        vec = null;
+                    }
+                    if (vec != null) {
+                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                    } else {
+                        drop(dragLayerX, dragLayerY);
+                    }
+                }
+                endDrag();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                cancelDrag();
+                break;
+        }
+
+        return mDragging;
+    }
+
+    /**
+     * Sets the view that should handle move events.
+     */
+    void setMoveTarget(View view) {
+        mMoveTarget = view;
+    }    
+
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
+    }
+
+    private void clearScrollRunnable() {
+        mHandler.removeCallbacks(mScrollRunnable);
+        if (mScrollState == SCROLL_WAITING_IN_ZONE) {
+            mScrollState = SCROLL_OUTSIDE_ZONE;
+            mScrollRunnable.setDirection(SCROLL_RIGHT);
+            mDragScroller.onExitScrollArea();
+            mLauncher.getDragLayer().onExitScrollArea();
+        }
+    }
+
+    private void handleMoveEvent(int x, int y) {
+        mDragObject.dragView.move(x, y);
+
+        // Drop on someone?
+        final int[] coordinates = mCoordinatesTemp;
+        DropTarget dropTarget = findDropTarget(x, y, coordinates);
+        mDragObject.x = coordinates[0];
+        mDragObject.y = coordinates[1];
+        checkTouchMove(dropTarget);
+
+        // Check if we are hovering over the scroll areas
+        mDistanceSinceScroll +=
+            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
+        mLastTouch[0] = x;
+        mLastTouch[1] = y;
+        checkScrollState(x, y);
+    }
+
+    public void forceTouchMove() {
+        int[] dummyCoordinates = mCoordinatesTemp;
+        DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
+        mDragObject.x = dummyCoordinates[0];
+        mDragObject.y = dummyCoordinates[1];
+        checkTouchMove(dropTarget);
+    }
+
+    private void checkTouchMove(DropTarget dropTarget) {
+        if (dropTarget != null) {
+            if (mLastDropTarget != dropTarget) {
+                if (mLastDropTarget != null) {
+                    mLastDropTarget.onDragExit(mDragObject);
+                }
+                dropTarget.onDragEnter(mDragObject);
+            }
+            dropTarget.onDragOver(mDragObject);
+        } else {
+            if (mLastDropTarget != null) {
+                mLastDropTarget.onDragExit(mDragObject);
+            }
+        }
+        mLastDropTarget = dropTarget;
+    }
+
+    private void checkScrollState(int x, int y) {
+        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
+        final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
+        final DragLayer dragLayer = mLauncher.getDragLayer();
+        final boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+        final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT;
+        final int backwardsDirection = isRtl ? SCROLL_LEFT : SCROLL_RIGHT;
+
+        if (x < mScrollZone) {
+            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+                mScrollState = SCROLL_WAITING_IN_ZONE;
+                if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
+                    dragLayer.onEnterScrollArea(forwardDirection);
+                    mScrollRunnable.setDirection(forwardDirection);
+                    mHandler.postDelayed(mScrollRunnable, delay);
+                }
+            }
+        } else if (x > mScrollView.getWidth() - mScrollZone) {
+            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+                mScrollState = SCROLL_WAITING_IN_ZONE;
+                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
+                    dragLayer.onEnterScrollArea(backwardsDirection);
+                    mScrollRunnable.setDirection(backwardsDirection);
+                    mHandler.postDelayed(mScrollRunnable, delay);
+                }
+            }
+        } else {
+            clearScrollRunnable();
+        }
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!mDragging) {
+            return false;
+        }
+
+        // Update the velocity tracker
+        acquireVelocityTrackerAndAddMovement(ev);
+
+        final int action = ev.getAction();
+        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+        final int dragLayerX = dragLayerPos[0];
+        final int dragLayerY = dragLayerPos[1];
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            // Remember where the motion event started
+            mMotionDownX = dragLayerX;
+            mMotionDownY = dragLayerY;
+
+            if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
+                mScrollState = SCROLL_WAITING_IN_ZONE;
+                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+            } else {
+                mScrollState = SCROLL_OUTSIDE_ZONE;
+            }
+            handleMoveEvent(dragLayerX, dragLayerY);
+            break;
+        case MotionEvent.ACTION_MOVE:
+            handleMoveEvent(dragLayerX, dragLayerY);
+            break;
+        case MotionEvent.ACTION_UP:
+            // Ensure that we've processed a move event at the current pointer location.
+            handleMoveEvent(dragLayerX, dragLayerY);
+            mHandler.removeCallbacks(mScrollRunnable);
+
+            if (mDragging) {
+                PointF vec = isFlingingToDelete(mDragObject.dragSource);
+                if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+                    vec = null;
+                }
+                if (vec != null) {
+                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                } else {
+                    drop(dragLayerX, dragLayerY);
+                }
+            }
+            endDrag();
+            break;
+        case MotionEvent.ACTION_CANCEL:
+            mHandler.removeCallbacks(mScrollRunnable);
+            cancelDrag();
+            break;
+        }
+
+        return true;
+    }
+
+    /**
+     * Determines whether the user flung the current item to delete it.
+     *
+     * @return the vector at which the item was flung, or null if no fling was detected.
+     */
+    private PointF isFlingingToDelete(DragSource source) {
+        if (mFlingToDeleteDropTarget == null) return null;
+        if (!source.supportsFlingToDelete()) return null;
+
+        ViewConfiguration config = ViewConfiguration.get(mLauncher);
+        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+                    mVelocityTracker.getYVelocity());
+            PointF upVec = new PointF(0f, -1f);
+            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+                    (vel.length() * upVec.length()));
+            if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+                return vel;
+            }
+        }
+        return null;
+    }
+
+    private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+        final int[] coordinates = mCoordinatesTemp;
+
+        mDragObject.x = coordinates[0];
+        mDragObject.y = coordinates[1];
+
+        // Clean up dragging on the target if it's not the current fling delete target otherwise,
+        // start dragging to it.
+        if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
+            mLastDropTarget.onDragExit(mDragObject);
+        }
+
+        // Drop onto the fling-to-delete target
+        boolean accepted = false;
+        mFlingToDeleteDropTarget.onDragEnter(mDragObject);
+        // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
+        // "drop"
+        mDragObject.dragComplete = true;
+        mFlingToDeleteDropTarget.onDragExit(mDragObject);
+        if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
+            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
+                    vel);
+            accepted = true;
+        }
+        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
+                accepted);
+    }
+
+    private void drop(float x, float y) {
+        final int[] coordinates = mCoordinatesTemp;
+        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
+
+        mDragObject.x = coordinates[0];
+        mDragObject.y = coordinates[1];
+        boolean accepted = false;
+        if (dropTarget != null) {
+            mDragObject.dragComplete = true;
+            dropTarget.onDragExit(mDragObject);
+            if (dropTarget.acceptDrop(mDragObject)) {
+                dropTarget.onDrop(mDragObject);
+                accepted = true;
+            }
+        }
+        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
+    }
+
+    private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
+        final Rect r = mRectTemp;
+
+        final ArrayList<DropTarget> dropTargets = mDropTargets;
+        final int count = dropTargets.size();
+        for (int i=count-1; i>=0; i--) {
+            DropTarget target = dropTargets.get(i);
+            if (!target.isDropEnabled())
+                continue;
+
+            target.getHitRectRelativeToDragLayer(r);
+
+            mDragObject.x = x;
+            mDragObject.y = y;
+            if (r.contains(x, y)) {
+
+                dropCoordinates[0] = x;
+                dropCoordinates[1] = y;
+                mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+
+                return target;
+            }
+        }
+        return null;
+    }
+
+    public void setDragScoller(DragScroller scroller) {
+        mDragScroller = scroller;
+    }
+
+    public void setWindowToken(IBinder token) {
+        mWindowToken = token;
+    }
+
+    /**
+     * Sets the drag listner which will be notified when a drag starts or ends.
+     */
+    public void addDragListener(DragListener l) {
+        mListeners.add(l);
+    }
+
+    /**
+     * Remove a previously installed drag listener.
+     */
+    public void removeDragListener(DragListener l) {
+        mListeners.remove(l);
+    }
+
+    /**
+     * Add a DropTarget to the list of potential places to receive drop events.
+     */
+    public void addDropTarget(DropTarget target) {
+        mDropTargets.add(target);
+    }
+
+    /**
+     * Don't send drop events to <em>target</em> any more.
+     */
+    public void removeDropTarget(DropTarget target) {
+        mDropTargets.remove(target);
+    }
+
+    /**
+     * Sets the current fling-to-delete drop target.
+     */
+    public void setFlingToDeleteDropTarget(DropTarget target) {
+        mFlingToDeleteDropTarget = target;
+    }
+
+    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+    }
+
+    private void releaseVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    /**
+     * Set which view scrolls for touch events near the edge of the screen.
+     */
+    public void setScrollView(View v) {
+        mScrollView = v;
+    }
+
+    DragView getDragView() {
+        return mDragObject.dragView;
+    }
+
+    private class ScrollRunnable implements Runnable {
+        private int mDirection;
+
+        ScrollRunnable() {
+        }
+
+        public void run() {
+            if (mDragScroller != null) {
+                if (mDirection == SCROLL_LEFT) {
+                    mDragScroller.scrollLeft();
+                } else {
+                    mDragScroller.scrollRight();
+                }
+                mScrollState = SCROLL_OUTSIDE_ZONE;
+                mDistanceSinceScroll = 0;
+                mDragScroller.onExitScrollArea();
+                mLauncher.getDragLayer().onExitScrollArea();
+
+                if (isDragging()) {
+                    // Check the scroll again so that we can requeue the scroller if necessary
+                    checkScrollState(mLastTouch[0], mLastTouch[1]);
+                }
+            }
+        }
+
+        void setDirection(int direction) {
+            mDirection = direction;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
new file mode 100644
index 0000000..89f8275
--- /dev/null
+++ b/src/com/android/launcher3/DragLayer.java
@@ -0,0 +1,841 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.*;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A ViewGroup that coordinates dragging across its descendants
+ */
+public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener {
+    private DragController mDragController;
+    private int[] mTmpXY = new int[2];
+
+    private int mXDown, mYDown;
+    private Launcher mLauncher;
+
+    // Variables relating to resizing widgets
+    private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
+            new ArrayList<AppWidgetResizeFrame>();
+    private AppWidgetResizeFrame mCurrentResizeFrame;
+
+    // Variables relating to animation of views after drop
+    private ValueAnimator mDropAnim = null;
+    private ValueAnimator mFadeOutAnim = null;
+    private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+    private DragView mDropView = null;
+    private int mAnchorViewInitialScrollX = 0;
+    private View mAnchorView = null;
+
+    private boolean mHoverPointClosesFolder = false;
+    private Rect mHitRect = new Rect();
+    private int mWorkspaceIndex = -1;
+    private int mQsbIndex = -1;
+    public static final int ANIMATION_END_DISAPPEAR = 0;
+    public static final int ANIMATION_END_FADE_OUT = 1;
+    public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
+
+    private TouchCompleteListener mTouchCompleteListener;
+
+    private final Rect mInsets = new Rect();
+
+    /**
+     * Used to create a new DragLayer from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attributes set containing the Workspace's customization values.
+     */
+    public DragLayer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Disable multitouch across the workspace/all apps/customize tray
+        setMotionEventSplittingEnabled(false);
+        setChildrenDrawingOrderEnabled(true);
+        setOnHierarchyChangeListener(this);
+
+        mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
+        mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
+    }
+
+    public void setup(Launcher launcher, DragController controller) {
+        mLauncher = launcher;
+        mDragController = controller;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        final int n = getChildCount();
+        for (int i = 0; i < n; i++) {
+            final View child = getChildAt(i);
+            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+            if (child instanceof Insettable) {
+                ((Insettable)child).setInsets(insets);
+            } else {
+                flp.topMargin += (insets.top - mInsets.top);
+                flp.leftMargin += (insets.left - mInsets.left);
+                flp.rightMargin += (insets.right - mInsets.right);
+                flp.bottomMargin += (insets.bottom - mInsets.bottom);
+            }
+            child.setLayoutParams(flp);
+        }
+        mInsets.set(insets);
+        return true; // I'll take it from here
+    }
+
+    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(folder, mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
+        Rect hitRect = new Rect();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        for (AppWidgetResizeFrame child: mResizeFrames) {
+            child.getHitRect(hitRect);
+            if (hitRect.contains(x, y)) {
+                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
+                    mCurrentResizeFrame = child;
+                    mXDown = x;
+                    mYDown = y;
+                    requestDisallowInterceptTouchEvent(true);
+                    return true;
+                }
+            }
+        }
+
+        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
+            if (currentFolder.isEditingName()) {
+                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
+                    currentFolder.dismissEditingName();
+                    return true;
+                }
+            }
+
+            getDescendantRectRelativeToSelf(currentFolder, hitRect);
+            if (!isEventOverFolder(currentFolder, ev)) {
+                mLauncher.closeFolder();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (handleTouchDown(ev, true)) {
+                return true;
+            }
+        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            if (mTouchCompleteListener != null) {
+                mTouchCompleteListener.onTouchComplete();
+            }
+            mTouchCompleteListener = null;
+        }
+        clearAllResizeFrames();
+        return mDragController.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptHoverEvent(MotionEvent ev) {
+        if (mLauncher == null || mLauncher.getWorkspace() == null) {
+            return false;
+        }
+        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        if (currentFolder == null) {
+            return false;
+        } else {
+                AccessibilityManager accessibilityManager = (AccessibilityManager)
+                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            if (accessibilityManager.isTouchExplorationEnabled()) {
+                final int action = ev.getAction();
+                boolean isOverFolder;
+                switch (action) {
+                    case MotionEvent.ACTION_HOVER_ENTER:
+                        isOverFolder = isEventOverFolder(currentFolder, ev);
+                        if (!isOverFolder) {
+                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+                            mHoverPointClosesFolder = true;
+                            return true;
+                        } else if (isOverFolder) {
+                            mHoverPointClosesFolder = false;
+                        } else {
+                            return true;
+                        }
+                    case MotionEvent.ACTION_HOVER_MOVE:
+                        isOverFolder = isEventOverFolder(currentFolder, ev);
+                        if (!isOverFolder && !mHoverPointClosesFolder) {
+                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+                            mHoverPointClosesFolder = true;
+                            return true;
+                        } else if (isOverFolder) {
+                            mHoverPointClosesFolder = false;
+                        } else {
+                            return true;
+                        }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (accessibilityManager.isEnabled()) {
+            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
+            AccessibilityEvent event = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            onInitializeAccessibilityEvent(event);
+            event.getText().add(getContext().getString(stringId));
+            accessibilityManager.sendAccessibilityEvent(event);
+        }
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        if (currentFolder != null) {
+            if (child == currentFolder) {
+                return super.onRequestSendAccessibilityEvent(child, event);
+            }
+            // Skip propagating onRequestSendAccessibilityEvent all for other children
+            // when a folder is open
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    @Override
+    public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
+        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        if (currentFolder != null) {
+            // Only add the folder as a child for accessibility when it is open
+            childrenForAccessibility.add(currentFolder);
+        } else {
+            super.addChildrenForAccessibility(childrenForAccessibility);
+        }
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        // If we've received this, we've already done the necessary handling
+        // in onInterceptHoverEvent. Return true to consume the event.
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        boolean handled = false;
+        int action = ev.getAction();
+
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (handleTouchDown(ev, false)) {
+                return true;
+            }
+        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            if (mTouchCompleteListener != null) {
+                mTouchCompleteListener.onTouchComplete();
+            }
+            mTouchCompleteListener = null;
+        }
+
+        if (mCurrentResizeFrame != null) {
+            handled = true;
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
+                    mCurrentResizeFrame.onTouchUp();
+                    mCurrentResizeFrame = null;
+            }
+        }
+        if (handled) return true;
+        return mDragController.onTouchEvent(ev);
+    }
+
+    /**
+     * Determine the rect of the descendant in this DragLayer's coordinates
+     *
+     * @param descendant The descendant whose coordinates we want to find.
+     * @param r The rect into which to place the results.
+     * @return The factor by which this descendant is scaled relative to this DragLayer.
+     */
+    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
+        mTmpXY[0] = 0;
+        mTmpXY[1] = 0;
+        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+
+        r.set(mTmpXY[0], mTmpXY[1],
+                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
+                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
+        return scale;
+    }
+
+    public float getLocationInDragLayer(View child, int[] loc) {
+        loc[0] = 0;
+        loc[1] = 0;
+        return getDescendantCoordRelativeToSelf(child, loc);
+    }
+
+    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
+        return getDescendantCoordRelativeToSelf(descendant, coord, false);
+    }
+
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
+     *          sometimes this is relevant as in a child's coordinates within the root descendant.
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
+            boolean includeRootScroll) {
+        return Utilities.getDescendantCoordRelativeToParent(descendant, this,
+                coord, includeRootScroll);
+    }
+
+    /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+     */
+    public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
+        return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
+    }
+
+    public void getViewRectRelativeToSelf(View v, Rect r) {
+        int[] loc = new int[2];
+        getLocationInWindow(loc);
+        int x = loc[0];
+        int y = loc[1];
+
+        v.getLocationInWindow(loc);
+        int vX = loc[0];
+        int vY = loc[1];
+
+        int left = vX - x;
+        int top = vY - y;
+        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return mDragController.dispatchUnhandledMove(focused, direction);
+    }
+
+    public static class LayoutParams extends FrameLayout.LayoutParams {
+        public int x, y;
+        public boolean customPosition = false;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public void setWidth(int width) {
+            this.width = width;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public void setHeight(int height) {
+            this.height = height;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        public void setX(int x) {
+            this.x = x;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public void setY(int y) {
+            this.y = y;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+            if (flp instanceof LayoutParams) {
+                final LayoutParams lp = (LayoutParams) flp;
+                if (lp.customPosition) {
+                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
+                }
+            }
+        }
+    }
+
+    public void clearAllResizeFrames() {
+        if (mResizeFrames.size() > 0) {
+            for (AppWidgetResizeFrame frame: mResizeFrames) {
+                frame.commitResize();
+                removeView(frame);
+            }
+            mResizeFrames.clear();
+        }
+    }
+
+    public boolean hasResizeFrames() {
+        return mResizeFrames.size() > 0;
+    }
+
+    public boolean isWidgetBeingResized() {
+        return mCurrentResizeFrame != null;
+    }
+
+    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
+            CellLayout cellLayout) {
+        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
+                widget, cellLayout, this);
+
+        LayoutParams lp = new LayoutParams(-1, -1);
+        lp.customPosition = true;
+
+        addView(resizeFrame, lp);
+        mResizeFrames.add(resizeFrame);
+
+        resizeFrame.snapToWidget(false);
+    }
+
+    public void animateViewIntoPosition(DragView dragView, final View child) {
+        animateViewIntoPosition(dragView, child, null);
+    }
+
+    public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
+            float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
+            int duration) {
+        Rect r = new Rect();
+        getViewRectRelativeToSelf(dragView, r);
+        final int fromX = r.left;
+        final int fromY = r.top;
+
+        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
+                onFinishRunnable, animationEndStyle, duration, null);
+    }
+
+    public void animateViewIntoPosition(DragView dragView, final View child,
+            final Runnable onFinishAnimationRunnable) {
+        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
+    }
+
+    public void animateViewIntoPosition(DragView dragView, final View child, int duration,
+            final Runnable onFinishAnimationRunnable, View anchorView) {
+        ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
+        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
+        parentChildren.measureChild(child);
+
+        Rect r = new Rect();
+        getViewRectRelativeToSelf(dragView, r);
+
+        int coord[] = new int[2];
+        float childScale = child.getScaleX();
+        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
+        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
+
+        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
+        // the correct coordinates (above) and use these to determine the final location
+        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
+        // We need to account for the scale of the child itself, as the above only accounts for
+        // for the scale in parents.
+        scale *= childScale;
+        int toX = coord[0];
+        int toY = coord[1];
+        if (child instanceof TextView) {
+            TextView tv = (TextView) child;
+
+            // The child may be scaled (always about the center of the view) so to account for it,
+            // we have to offset the position by the scaled size.  Once we do that, we can center
+            // the drag view about the scaled child view.
+            toY += Math.round(scale * tv.getPaddingTop());
+            toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
+            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+        } else if (child instanceof FolderIcon) {
+            // Account for holographic blur padding on the drag view
+            toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
+            toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
+            toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
+            // Center in the x coordinate about the target's drawable
+            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+        } else {
+            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
+            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
+                    - child.getMeasuredWidth()))) / 2;
+        }
+
+        final int fromX = r.left;
+        final int fromY = r.top;
+        child.setVisibility(INVISIBLE);
+        Runnable onCompleteRunnable = new Runnable() {
+            public void run() {
+                child.setVisibility(VISIBLE);
+                if (onFinishAnimationRunnable != null) {
+                    onFinishAnimationRunnable.run();
+                }
+            }
+        };
+        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+                onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
+    }
+
+    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
+            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
+            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+            int animationEndStyle, int duration, View anchorView) {
+        Rect from = new Rect(fromX, fromY, fromX +
+                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
+        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
+        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
+                null, null, onCompleteRunnable, animationEndStyle, anchorView);
+    }
+
+    /**
+     * This method animates a view at the end of a drag and drop animation.
+     *
+     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
+     *        doesn't need to be a child of DragLayer.
+     * @param from The initial location of the view. Only the left and top parameters are used.
+     * @param to The final location of the view. Only the left and top parameters are used. This
+     *        location doesn't account for scaling, and so should be centered about the desired
+     *        final location (including scaling).
+     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
+     * @param finalScale The final scale of the view. The view is scaled about its center.
+     * @param duration The duration of the animation.
+     * @param motionInterpolator The interpolator to use for the location of the view.
+     * @param alphaInterpolator The interpolator to use for the alpha of the view.
+     * @param onCompleteRunnable Optional runnable to run on animation completion.
+     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
+     *        the runnable will execute after the view is faded out.
+     * @param anchorView If not null, this represents the view which the animated view stays
+     *        anchored to in case scrolling is currently taking place. Note: currently this is
+     *        only used for the X dimension for the case of the workspace.
+     */
+    public void animateView(final DragView view, final Rect from, final Rect to,
+            final float finalAlpha, final float initScaleX, final float initScaleY,
+            final float finalScaleX, final float finalScaleY, int duration,
+            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
+            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
+
+        // Calculate the duration of the animation based on the object's distance
+        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
+                Math.pow(to.top - from.top, 2));
+        final Resources res = getResources();
+        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
+
+        // If duration < 0, this is a cue to compute the duration based on the distance
+        if (duration < 0) {
+            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+            if (dist < maxDist) {
+                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+            }
+            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
+        }
+
+        // Fall back to cubic ease out interpolator for the animation if none is specified
+        TimeInterpolator interpolator = null;
+        if (alphaInterpolator == null || motionInterpolator == null) {
+            interpolator = mCubicEaseOutInterpolator;
+        }
+
+        // Animate the view
+        final float initAlpha = view.getAlpha();
+        final float dropViewScale = view.getScaleX();
+        AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final float percent = (Float) animation.getAnimatedValue();
+                final int width = view.getMeasuredWidth();
+                final int height = view.getMeasuredHeight();
+
+                float alphaPercent = alphaInterpolator == null ? percent :
+                        alphaInterpolator.getInterpolation(percent);
+                float motionPercent = motionInterpolator == null ? percent :
+                        motionInterpolator.getInterpolation(percent);
+
+                float initialScaleX = initScaleX * dropViewScale;
+                float initialScaleY = initScaleY * dropViewScale;
+                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
+                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
+                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
+
+                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
+                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
+
+                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
+                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
+
+                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
+                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
+                int yPos = y - mDropView.getScrollY();
+
+                mDropView.setTranslationX(xPos);
+                mDropView.setTranslationY(yPos);
+                mDropView.setScaleX(scaleX);
+                mDropView.setScaleY(scaleY);
+                mDropView.setAlpha(alpha);
+            }
+        };
+        animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
+                anchorView);
+    }
+
+    public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
+            TimeInterpolator interpolator, final Runnable onCompleteRunnable,
+            final int animationEndStyle, View anchorView) {
+        // Clean up the previous animations
+        if (mDropAnim != null) mDropAnim.cancel();
+        if (mFadeOutAnim != null) mFadeOutAnim.cancel();
+
+        // Show the drop view if it was previously hidden
+        mDropView = view;
+        mDropView.cancelAnimation();
+        mDropView.resetLayoutParams();
+
+        // Set the anchor view if the page is scrolling
+        if (anchorView != null) {
+            mAnchorViewInitialScrollX = anchorView.getScrollX();
+        }
+        mAnchorView = anchorView;
+
+        // Create and start the animation
+        mDropAnim = new ValueAnimator();
+        mDropAnim.setInterpolator(interpolator);
+        mDropAnim.setDuration(duration);
+        mDropAnim.setFloatValues(0f, 1f);
+        mDropAnim.addUpdateListener(updateCb);
+        mDropAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+                switch (animationEndStyle) {
+                case ANIMATION_END_DISAPPEAR:
+                    clearAnimatedView();
+                    break;
+                case ANIMATION_END_FADE_OUT:
+                    fadeOutDragView();
+                    break;
+                case ANIMATION_END_REMAIN_VISIBLE:
+                    break;
+                }
+            }
+        });
+        mDropAnim.start();
+    }
+
+    public void clearAnimatedView() {
+        if (mDropAnim != null) {
+            mDropAnim.cancel();
+        }
+        if (mDropView != null) {
+            mDragController.onDeferredEndDrag(mDropView);
+        }
+        mDropView = null;
+        invalidate();
+    }
+
+    public View getAnimatedView() {
+        return mDropView;
+    }
+
+    private void fadeOutDragView() {
+        mFadeOutAnim = new ValueAnimator();
+        mFadeOutAnim.setDuration(150);
+        mFadeOutAnim.setFloatValues(0f, 1f);
+        mFadeOutAnim.removeAllUpdateListeners();
+        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final float percent = (Float) animation.getAnimatedValue();
+
+                float alpha = 1 - percent;
+                mDropView.setAlpha(alpha);
+            }
+        });
+        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                if (mDropView != null) {
+                    mDragController.onDeferredEndDrag(mDropView);
+                }
+                mDropView = null;
+                invalidate();
+            }
+        });
+        mFadeOutAnim.start();
+    }
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        updateChildIndices();
+    }
+
+    @Override
+    public void onChildViewRemoved(View parent, View child) {
+        updateChildIndices();
+    }
+
+    private void updateChildIndices() {
+        if (mLauncher != null) {
+            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
+            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
+        }
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        // TODO: We have turned off this custom drawing order because it now effects touch
+        // dispatch order. We need to sort that issue out and then decide how to go about this.
+        if (true || LauncherAppState.isScreenLandscape(getContext()) ||
+                mWorkspaceIndex == -1 || mQsbIndex == -1 ||
+                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
+            return i;
+        }
+
+        // This ensures that the workspace is drawn above the hotseat and qsb,
+        // except when the workspace is drawing a background gradient, in which
+        // case we want the workspace to stay behind these elements.
+        if (i == mQsbIndex) {
+            return mWorkspaceIndex;
+        } else if (i == mWorkspaceIndex) {
+            return mQsbIndex;
+        } else {
+            return i;
+        }
+    }
+
+    private boolean mInScrollArea;
+    private Drawable mLeftHoverDrawable;
+    private Drawable mRightHoverDrawable;
+
+    void onEnterScrollArea(int direction) {
+        mInScrollArea = true;
+        invalidate();
+    }
+
+    void onExitScrollArea() {
+        mInScrollArea = false;
+        invalidate();
+    }
+
+    /**
+     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+     */
+    private boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mInScrollArea && !LauncherAppState.getInstance().isScreenLarge()) {
+            Workspace workspace = mLauncher.getWorkspace();
+            int width = getMeasuredWidth();
+            Rect childRect = new Rect();
+            getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
+
+            int page = workspace.getNextPage();
+            final boolean isRtl = isLayoutRtl();
+            CellLayout leftPage = (CellLayout) workspace.getChildAt(isRtl ? page + 1 : page - 1);
+            CellLayout rightPage = (CellLayout) workspace.getChildAt(isRtl ? page - 1 : page + 1);
+
+            if (leftPage != null && leftPage.getIsDragOverlapping()) {
+                mLeftHoverDrawable.setBounds(0, childRect.top,
+                        mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
+                mLeftHoverDrawable.draw(canvas);
+            } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
+                mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
+                        childRect.top, width, childRect.bottom);
+                mRightHoverDrawable.draw(canvas);
+            }
+        }
+    }
+
+    public void setTouchCompleteListener(TouchCompleteListener listener) {
+        mTouchCompleteListener = listener;
+    }
+
+    public interface TouchCompleteListener {
+        public void onTouchComplete();
+    }
+}
diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/DragScroller.java
new file mode 100644
index 0000000..e261f15
--- /dev/null
+++ b/src/com/android/launcher3/DragScroller.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+/**
+ * Handles scrolling while dragging
+ *
+ */
+public interface DragScroller {
+    void scrollLeft();
+    void scrollRight();
+
+    /**
+     * The touch point has entered the scroll area; a scroll is imminent.
+     * This event will only occur while a drag is active.
+     *
+     * @param direction The scroll direction
+     */
+    boolean onEnterScrollArea(int x, int y, int direction);
+
+    /**
+     * The touch point has left the scroll area.
+     * NOTE: This may not be called, if a drop occurs inside the scroll area.
+     */
+    boolean onExitScrollArea();
+}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
new file mode 100644
index 0000000..2ef99ae
--- /dev/null
+++ b/src/com/android/launcher3/DragSource.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.view.View;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+/**
+ * Interface defining an object that can originate a drag.
+ *
+ */
+public interface DragSource {
+    /**
+     * @return whether items dragged from this source supports
+     */
+    boolean supportsFlingToDelete();
+
+    /**
+     * A callback specifically made back to the source after an item from this source has been flung
+     * to be deleted on a DropTarget.  In such a situation, this method will be called after
+     * onDropCompleted, and more importantly, after the fling animation has completed.
+     */
+    void onFlingToDeleteCompleted();
+
+    /**
+     * A callback made back to the source after an item from this source has been dropped on a
+     * DropTarget.
+     */
+    void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
+}
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
new file mode 100644
index 0000000..686cf62
--- /dev/null
+++ b/src/com/android/launcher3/DragView.java
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+
+package com.android.launcher3;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.R;
+
+public class DragView extends View {
+    private static float sDragAlpha = 1f;
+
+    private Bitmap mBitmap;
+    private Bitmap mCrossFadeBitmap;
+    private Paint mPaint;
+    private int mRegistrationX;
+    private int mRegistrationY;
+
+    private Point mDragVisualizeOffset = null;
+    private Rect mDragRegion = null;
+    private DragLayer mDragLayer = null;
+    private boolean mHasDrawn = false;
+    private float mCrossFadeProgress = 0f;
+
+    ValueAnimator mAnim;
+    private float mOffsetX = 0.0f;
+    private float mOffsetY = 0.0f;
+    private float mInitialScale = 1f;
+
+    /**
+     * Construct the drag view.
+     * <p>
+     * The registration point is the point inside our view that the touch events should
+     * be centered upon.
+     *
+     * @param launcher The Launcher instance
+     * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
+     * @param registrationX The x coordinate of the registration point.
+     * @param registrationY The y coordinate of the registration point.
+     */
+    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
+            int left, int top, int width, int height, final float initialScale) {
+        super(launcher);
+        mDragLayer = launcher.getDragLayer();
+        mInitialScale = initialScale;
+
+        final Resources res = getResources();
+        final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX);
+        final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY);
+        final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
+        final float scale = (width + scaleDps) / width;
+
+        // Set the initial scale to avoid any jumps
+        setScaleX(initialScale);
+        setScaleY(initialScale);
+
+        // Animate the view into the correct position
+        mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        mAnim.setDuration(150);
+        mAnim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final float value = (Float) animation.getAnimatedValue();
+
+                final int deltaX = (int) ((value * offsetX) - mOffsetX);
+                final int deltaY = (int) ((value * offsetY) - mOffsetY);
+
+                mOffsetX += deltaX;
+                mOffsetY += deltaY;
+                setScaleX(initialScale + (value * (scale - initialScale)));
+                setScaleY(initialScale + (value * (scale - initialScale)));
+                if (sDragAlpha != 1f) {
+                    setAlpha(sDragAlpha * value + (1f - value));
+                }
+
+                if (getParent() == null) {
+                    animation.cancel();
+                } else {
+                    setTranslationX(getTranslationX() + deltaX);
+                    setTranslationY(getTranslationY() + deltaY);
+                }
+            }
+        });
+
+        mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
+        setDragRegion(new Rect(0, 0, width, height));
+
+        // The point in our scaled bitmap that the touch events are located
+        mRegistrationX = registrationX;
+        mRegistrationY = registrationY;
+
+        // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
+        int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        measure(ms, ms);
+        mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+    }
+
+    public float getOffsetY() {
+        return mOffsetY;
+    }
+
+    public int getDragRegionLeft() {
+        return mDragRegion.left;
+    }
+
+    public int getDragRegionTop() {
+        return mDragRegion.top;
+    }
+
+    public int getDragRegionWidth() {
+        return mDragRegion.width();
+    }
+
+    public int getDragRegionHeight() {
+        return mDragRegion.height();
+    }
+
+    public void setDragVisualizeOffset(Point p) {
+        mDragVisualizeOffset = p;
+    }
+
+    public Point getDragVisualizeOffset() {
+        return mDragVisualizeOffset;
+    }
+
+    public void setDragRegion(Rect r) {
+        mDragRegion = r;
+    }
+
+    public Rect getDragRegion() {
+        return mDragRegion;
+    }
+
+    public float getInitialScale() {
+        return mInitialScale;
+    }
+
+    public void updateInitialScaleToCurrentScale() {
+        mInitialScale = getScaleX();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        @SuppressWarnings("all") // suppress dead code warning
+        final boolean debug = false;
+        if (debug) {
+            Paint p = new Paint();
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(0x66ffffff);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), p);
+        }
+
+        mHasDrawn = true;
+        boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+        if (crossFade) {
+            int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
+            mPaint.setAlpha(alpha);
+        }
+        canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+        if (crossFade) {
+            mPaint.setAlpha((int) (255 * mCrossFadeProgress));
+            canvas.save();
+            float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
+            float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+            canvas.scale(sX, sY);
+            canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+            canvas.restore();
+        }
+    }
+
+    public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
+        mCrossFadeBitmap = crossFadeBitmap;
+    }
+
+    public void crossFade(int duration) {
+        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        va.setDuration(duration);
+        va.setInterpolator(new DecelerateInterpolator(1.5f));
+        va.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mCrossFadeProgress = animation.getAnimatedFraction();
+            }
+        });
+        va.start();
+    }
+
+    public void setColor(int color) {
+        if (mPaint == null) {
+            mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+        }
+        if (color != 0) {
+            mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+        } else {
+            mPaint.setColorFilter(null);
+        }
+        invalidate();
+    }
+
+    public boolean hasDrawn() {
+        return mHasDrawn;
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+        mPaint.setAlpha((int) (255 * alpha));
+        invalidate();
+    }
+
+    /**
+     * Create a window containing this view and show it.
+     *
+     * @param windowToken obtained from v.getWindowToken() from one of your views
+     * @param touchX the x coordinate the user touched in DragLayer coordinates
+     * @param touchY the y coordinate the user touched in DragLayer coordinates
+     */
+    public void show(int touchX, int touchY) {
+        mDragLayer.addView(this);
+
+        // Start the pick-up animation
+        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
+        lp.width = mBitmap.getWidth();
+        lp.height = mBitmap.getHeight();
+        lp.customPosition = true;
+        setLayoutParams(lp);
+        setTranslationX(touchX - mRegistrationX);
+        setTranslationY(touchY - mRegistrationY);
+        // Post the animation to skip other expensive work happening on the first frame
+        post(new Runnable() {
+                public void run() {
+                    mAnim.start();
+                }
+            });
+    }
+
+    public void cancelAnimation() {
+        if (mAnim != null && mAnim.isRunning()) {
+            mAnim.cancel();
+        }
+    }
+
+    public void resetLayoutParams() {
+        mOffsetX = mOffsetY = 0;
+        requestLayout();
+    }
+
+    /**
+     * Move the window containing this view.
+     *
+     * @param touchX the x coordinate the user touched in DragLayer coordinates
+     * @param touchY the y coordinate the user touched in DragLayer coordinates
+     */
+    void move(int touchX, int touchY) {
+        setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
+        setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
+    }
+
+    void remove() {
+        if (getParent() != null) {
+            mDragLayer.removeView(DragView.this);
+        }
+    }
+}
+
diff --git a/src/com/android/launcher3/DrawableStateProxyView.java b/src/com/android/launcher3/DrawableStateProxyView.java
new file mode 100644
index 0000000..0758de1
--- /dev/null
+++ b/src/com/android/launcher3/DrawableStateProxyView.java
@@ -0,0 +1,71 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.R;
+
+public class DrawableStateProxyView extends LinearLayout {
+
+    private View mView;
+    private int mViewId;
+
+    public DrawableStateProxyView(Context context) {
+        this(context, null);
+    }
+
+    public DrawableStateProxyView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+
+    public DrawableStateProxyView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableStateProxyView,
+                defStyle, 0);
+        mViewId = a.getResourceId(R.styleable.DrawableStateProxyView_sourceViewId, -1);
+        a.recycle();
+
+        setFocusable(false);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if (mView == null) {
+            View parent = (View) getParent();
+            mView = parent.findViewById(mViewId);
+        }
+        if (mView != null) {
+            mView.setPressed(isPressed());
+            mView.setHovered(isHovered());
+        }
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
new file mode 100644
index 0000000..64f0ac8
--- /dev/null
+++ b/src/com/android/launcher3/DropTarget.java
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.Log;
+
+/**
+ * Interface defining an object that can receive a drag.
+ *
+ */
+public interface DropTarget {
+
+    public static final String TAG = "DropTarget";
+
+    class DragObject {
+        public int x = -1;
+        public int y = -1;
+
+        /** X offset from the upper-left corner of the cell to where we touched.  */
+        public int xOffset = -1;
+
+        /** Y offset from the upper-left corner of the cell to where we touched.  */
+        public int yOffset = -1;
+
+        /** This indicates whether a drag is in final stages, either drop or cancel. It
+         * differentiates onDragExit, since this is called when the drag is ending, above
+         * the current drag target, or when the drag moves off the current drag object.
+         */
+        public boolean dragComplete = false;
+
+        /** The view that moves around while you drag.  */
+        public DragView dragView = null;
+
+        /** The data associated with the object being dragged */
+        public Object dragInfo = null;
+
+        /** Where the drag originated */
+        public DragSource dragSource = null;
+
+        /** Post drag animation runnable */
+        public Runnable postAnimationRunnable = null;
+
+        /** Indicates that the drag operation was cancelled */
+        public boolean cancelled = false;
+
+        /** Defers removing the DragView from the DragLayer until after the drop animation. */
+        public boolean deferDragViewCleanupPostAnimation = true;
+
+        public DragObject() {
+        }
+    }
+
+    public static class DragEnforcer implements DragController.DragListener {
+        int dragParity = 0;
+
+        public DragEnforcer(Context context) {
+            Launcher launcher = (Launcher) context;
+            launcher.getDragController().addDragListener(this);
+        }
+
+        void onDragEnter() {
+            dragParity++;
+            if (dragParity != 1) {
+                Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity);
+            }
+        }
+
+        void onDragExit() {
+            dragParity--;
+            if (dragParity != 0) {
+                Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity);
+            }
+        }
+
+        @Override
+        public void onDragStart(DragSource source, Object info, int dragAction) {
+            if (dragParity != 0) {
+                Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity);
+            }
+        }
+
+        @Override
+        public void onDragEnd() {
+            if (dragParity != 0) {
+                Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity);
+            }
+        }
+    }
+
+    /**
+     * Used to temporarily disable certain drop targets
+     *
+     * @return boolean specifying whether this drop target is currently enabled
+     */
+    boolean isDropEnabled();
+
+    /**
+     * Handle an object being dropped on the DropTarget
+     * 
+     * @param source DragSource where the drag started
+     * @param x X coordinate of the drop location
+     * @param y Y coordinate of the drop location
+     * @param xOffset Horizontal offset with the object being dragged where the original
+     *          touch happened
+     * @param yOffset Vertical offset with the object being dragged where the original
+     *          touch happened
+     * @param dragView The DragView that's being dragged around on screen.
+     * @param dragInfo Data associated with the object being dragged
+     *
+     */
+    void onDrop(DragObject dragObject);
+
+    void onDragEnter(DragObject dragObject);
+
+    void onDragOver(DragObject dragObject);
+
+    void onDragExit(DragObject dragObject);
+
+    /**
+     * Handle an object being dropped as a result of flinging to delete and will be called in place
+     * of onDrop().  (This is only called on objects that are set as the DragController's
+     * fling-to-delete target.
+     */
+    void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec);
+
+    /**
+     * Check if a drop action can occur at, or near, the requested location.
+     * This will be called just before onDrop.
+     * 
+     * @param source DragSource where the drag started
+     * @param x X coordinate of the drop location
+     * @param y Y coordinate of the drop location
+     * @param xOffset Horizontal offset with the object being dragged where the
+     *            original touch happened
+     * @param yOffset Vertical offset with the object being dragged where the
+     *            original touch happened
+     * @param dragView The DragView that's being dragged around on screen.
+     * @param dragInfo Data associated with the object being dragged
+     * @return True if the drop will be accepted, false otherwise.
+     */
+    boolean acceptDrop(DragObject dragObject);
+
+    // These methods are implemented in Views
+    void getHitRectRelativeToDragLayer(Rect outRect);
+    void getLocationInDragLayer(int[] loc);
+    int getLeft();
+    int getTop();
+}
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
new file mode 100644
index 0000000..4776c86
--- /dev/null
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -0,0 +1,560 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+
+class DeviceProfileQuery {
+    float widthDps;
+    float heightDps;
+    float value;
+    PointF dimens;
+
+    DeviceProfileQuery(float w, float h, float v) {
+        widthDps = w;
+        heightDps = h;
+        value = v;
+        dimens = new PointF(w, h);
+    }
+}
+
+class DeviceProfile {
+    String name;
+    float minWidthDps;
+    float minHeightDps;
+    float numRows;
+    float numColumns;
+    float iconSize;
+    float iconTextSize;
+    float numHotseatIcons;
+    float hotseatIconSize;
+
+    boolean isLandscape;
+    boolean isTablet;
+    boolean isLargeTablet;
+    boolean transposeLayoutWithOrientation;
+
+    int desiredWorkspaceLeftRightMarginPx;
+    int edgeMarginPx;
+    Rect defaultWidgetPadding;
+
+    int widthPx;
+    int heightPx;
+    int availableWidthPx;
+    int availableHeightPx;
+    int iconSizePx;
+    int iconTextSizePx;
+    int cellWidthPx;
+    int cellHeightPx;
+    int folderBackgroundOffset;
+    int folderIconSizePx;
+    int folderCellWidthPx;
+    int folderCellHeightPx;
+    int hotseatCellWidthPx;
+    int hotseatCellHeightPx;
+    int hotseatIconSizePx;
+    int hotseatBarHeightPx;
+    int hotseatAllAppsRank;
+    int allAppsNumRows;
+    int allAppsNumCols;
+    int searchBarSpaceWidthPx;
+    int searchBarSpaceMaxWidthPx;
+    int searchBarSpaceHeightPx;
+    int searchBarHeightPx;
+    int pageIndicatorHeightPx;
+
+    DeviceProfile(String n, float w, float h, float r, float c,
+                  float is, float its, float hs, float his) {
+        // Ensure that we have an odd number of hotseat items (since we need to place all apps)
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
+            throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
+        }
+
+        name = n;
+        minWidthDps = w;
+        minHeightDps = h;
+        numRows = r;
+        numColumns = c;
+        iconSize = is;
+        iconTextSize = its;
+        numHotseatIcons = hs;
+        hotseatIconSize = his;
+    }
+
+    DeviceProfile(Context context,
+                  ArrayList<DeviceProfile> profiles,
+                  float minWidth, float minHeight,
+                  int wPx, int hPx,
+                  int awPx, int ahPx,
+                  Resources resources) {
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        ArrayList<DeviceProfileQuery> points =
+                new ArrayList<DeviceProfileQuery>();
+        transposeLayoutWithOrientation =
+                resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+        minWidthDps = minWidth;
+        minHeightDps = minHeight;
+
+        ComponentName cn = new ComponentName(context.getPackageName(),
+                this.getClass().getName());
+        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+        edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
+        pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
+
+        // Interpolate the rows
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+        }
+        numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the columns
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
+        }
+        numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the icon size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+        }
+        iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+        iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
+
+        // Interpolate the icon text size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+        }
+        iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+        iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
+
+        // Interpolate the hotseat size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+        }
+        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the hotseat icon size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+        }
+        // Hotseat
+        hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+        hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
+        hotseatAllAppsRank = (int) (numColumns / 2);
+
+        // Calculate other vars based on Configuration
+        updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
+
+        // Search Bar
+        searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
+        searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+        searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+        searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
+
+        // Calculate the actual text height
+        Paint textPaint = new Paint();
+        textPaint.setTextSize(iconTextSizePx);
+        FontMetrics fm = textPaint.getFontMetrics();
+        cellWidthPx = iconSizePx;
+        cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
+
+        // At this point, if the cells do not fit into the available height, then we need
+        // to shrink the icon size
+        /*
+        Rect padding = getWorkspacePadding(isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+        int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom;
+        if (h > availableHeightPx) {
+            float delta = h - availableHeightPx;
+            int deltaPx = (int) Math.ceil(delta / numRows);
+            iconSizePx -= deltaPx;
+            iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm);
+            cellWidthPx = iconSizePx;
+            cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
+        }
+        */
+
+        // Hotseat
+        hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+        hotseatCellWidthPx = iconSizePx;
+        hotseatCellHeightPx = iconSizePx;
+
+        // Folder
+        folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+        folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);
+        folderBackgroundOffset = -edgeMarginPx;
+        folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+    }
+
+    void updateFromConfiguration(Resources resources, int wPx, int hPx,
+                                 int awPx, int ahPx) {
+        isLandscape = (resources.getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE);
+        isTablet = resources.getBoolean(R.bool.is_tablet);
+        isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
+        widthPx = wPx;
+        heightPx = hPx;
+        availableWidthPx = awPx;
+        availableHeightPx = ahPx;
+
+        Rect padding = getWorkspacePadding(isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+        int pageIndicatorOffset =
+            resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+        if (isLandscape) {
+            allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
+                    (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
+        } else {
+            allAppsNumRows = (int) numRows + 1;
+        }
+        allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
+                (iconSizePx + 2 * edgeMarginPx);
+    }
+
+    private float dist(PointF p0, PointF p1) {
+        return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
+                (p1.y-p0.y)*(p1.y-p0.y));
+    }
+
+    private float weight(PointF a, PointF b,
+                        float pow) {
+        float d = dist(a, b);
+        if (d == 0f) {
+            return Float.POSITIVE_INFINITY;
+        }
+        return (float) (1f / Math.pow(d, pow));
+    }
+
+    private float invDistWeightedInterpolate(float width, float height,
+                ArrayList<DeviceProfileQuery> points) {
+        float sum = 0;
+        float weights = 0;
+        float pow = 5;
+        float kNearestNeighbors = 3;
+        final PointF xy = new PointF(width, height);
+
+        ArrayList<DeviceProfileQuery> pointsByNearness = points;
+        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+            }
+        });
+
+        for (int i = 0; i < pointsByNearness.size(); ++i) {
+            DeviceProfileQuery p = pointsByNearness.get(i);
+            if (i < kNearestNeighbors) {
+                float w = weight(xy, p.dimens, pow);
+                if (w == Float.POSITIVE_INFINITY) {
+                    return p.value;
+                }
+                weights += w;
+            }
+        }
+
+        for (int i = 0; i < pointsByNearness.size(); ++i) {
+            DeviceProfileQuery p = pointsByNearness.get(i);
+            if (i < kNearestNeighbors) {
+                float w = weight(xy, p.dimens, pow);
+                sum += w * p.value / weights;
+            }
+        }
+
+        return sum;
+    }
+
+    Rect getWorkspacePadding(int orientation) {
+        Rect padding = new Rect();
+        if (orientation == CellLayout.LANDSCAPE &&
+                transposeLayoutWithOrientation) {
+            // Pad the left and right of the workspace with search/hotseat bar sizes
+            padding.set(searchBarSpaceHeightPx, edgeMarginPx,
+                    hotseatBarHeightPx, edgeMarginPx);
+        } else {
+            if (isTablet()) {
+                // Pad the left and right of the workspace to ensure consistent spacing
+                // between all icons
+                int width = (orientation == CellLayout.LANDSCAPE)
+                        ? Math.max(widthPx, heightPx)
+                        : Math.min(widthPx, heightPx);
+                // XXX: If the icon size changes across orientations, we will have to take
+                //      that into account here too.
+                int gap = (int) ((width - 2 * edgeMarginPx -
+                        (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+                padding.set(edgeMarginPx + gap,
+                        searchBarSpaceHeightPx,
+                        edgeMarginPx + gap,
+                        hotseatBarHeightPx + pageIndicatorHeightPx);
+            } else {
+                // Pad the top and bottom of the workspace with search/hotseat bar sizes
+                padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+                        searchBarSpaceHeightPx,
+                        desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
+                        hotseatBarHeightPx + pageIndicatorHeightPx);
+            }
+        }
+        return padding;
+    }
+
+    // The rect returned will be extended to below the system ui that covers the workspace
+    Rect getHotseatRect() {
+        if (isVerticalBarLayout()) {
+            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+                    Integer.MAX_VALUE, availableHeightPx);
+        } else {
+            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+                    availableWidthPx, Integer.MAX_VALUE);
+        }
+    }
+
+    int calculateCellWidth(int width, int countX) {
+        return width / countX;
+    }
+    int calculateCellHeight(int height, int countY) {
+        return height / countY;
+    }
+
+    boolean isPhone() {
+        return !isTablet && !isLargeTablet;
+    }
+    boolean isTablet() {
+        return isTablet;
+    }
+    boolean isLargeTablet() {
+        return isLargeTablet;
+    }
+
+    boolean isVerticalBarLayout() {
+        return isLandscape && transposeLayoutWithOrientation;
+    }
+
+    public void layout(Launcher launcher) {
+        FrameLayout.LayoutParams lp;
+        Resources res = launcher.getResources();
+        boolean hasVerticalBarLayout = isVerticalBarLayout();
+
+        // Layout the search bar space
+        View searchBar = launcher.getSearchBar();
+        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical search bar
+            lp.gravity = Gravity.TOP | Gravity.LEFT;
+            lp.width = searchBarSpaceHeightPx;
+            lp.height = LayoutParams.MATCH_PARENT;
+            searchBar.setPadding(
+                    0, 2 * edgeMarginPx, 0,
+                    2 * edgeMarginPx);
+        } else {
+            // Horizontal search bar
+            lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+            lp.width = searchBarSpaceWidthPx;
+            lp.height = searchBarSpaceHeightPx;
+            searchBar.setPadding(
+                    2 * edgeMarginPx,
+                    2 * edgeMarginPx,
+                    2 * edgeMarginPx, 0);
+        }
+        searchBar.setLayoutParams(lp);
+
+        // Layout the search bar
+        View qsbBar = launcher.getQsbBar();
+        LayoutParams vglp = qsbBar.getLayoutParams();
+        vglp.width = LayoutParams.MATCH_PARENT;
+        vglp.height = LayoutParams.MATCH_PARENT;
+        qsbBar.setLayoutParams(vglp);
+
+        // Layout the voice proxy
+        View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
+        if (voiceButtonProxy != null) {
+            if (hasVerticalBarLayout) {
+                // TODO: MOVE THIS INTO SEARCH BAR MEASURE
+            } else {
+                lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
+                lp.gravity = Gravity.TOP | Gravity.END;
+                lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
+                        2 * iconSizePx;
+                lp.height = searchBarSpaceHeightPx;
+            }
+        }
+
+        // Layout the workspace
+        View workspace = launcher.findViewById(R.id.workspace);
+        lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
+        lp.gravity = Gravity.CENTER;
+        Rect padding = getWorkspacePadding(isLandscape
+                ? CellLayout.LANDSCAPE
+                : CellLayout.PORTRAIT);
+        workspace.setPadding(padding.left, padding.top,
+                padding.right, padding.bottom);
+        workspace.setLayoutParams(lp);
+
+        // Layout the hotseat
+        View hotseat = launcher.findViewById(R.id.hotseat);
+        lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical hotseat
+            lp.gravity = Gravity.RIGHT;
+            lp.width = hotseatBarHeightPx;
+            lp.height = LayoutParams.MATCH_PARENT;
+            hotseat.setPadding(0, 2 * edgeMarginPx,
+                    2 * edgeMarginPx, 2 * edgeMarginPx);
+        } else if (isTablet()) {
+            // Pad the hotseat with the grid gap calculated above
+            int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
+                    (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+            int gridWidth = (int) ((numColumns * cellWidthPx) +
+                    ((numColumns - 1) * gridGap));
+            int hotseatGap = (int) Math.max(0,
+                    (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
+                            / (numHotseatIcons - 1));
+            lp.gravity = Gravity.BOTTOM;
+            lp.width = LayoutParams.MATCH_PARENT;
+            lp.height = hotseatBarHeightPx;
+            hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
+                    2 * edgeMarginPx + gridGap + hotseatGap,
+                    2 * edgeMarginPx);
+        } else {
+            // For phones, layout the hotseat without any bottom margin
+            // to ensure that we have space for the folders
+            lp.gravity = Gravity.BOTTOM;
+            lp.width = LayoutParams.MATCH_PARENT;
+            lp.height = hotseatBarHeightPx;
+            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
+                    2 * edgeMarginPx, 0);
+        }
+        hotseat.setLayoutParams(lp);
+
+        // Layout the page indicators
+        View pageIndicator = launcher.findViewById(R.id.page_indicator);
+        if (pageIndicator != null) {
+            if (hasVerticalBarLayout) {
+                // Hide the page indicators when we have vertical search/hotseat
+                pageIndicator.setVisibility(View.GONE);
+            } else {
+                // Put the page indicators above the hotseat
+                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                lp.width = LayoutParams.WRAP_CONTENT;
+                lp.height = LayoutParams.WRAP_CONTENT;
+                lp.bottomMargin = hotseatBarHeightPx;
+                pageIndicator.setLayoutParams(lp);
+            }
+        }
+    }
+}
+
+public class DynamicGrid {
+    @SuppressWarnings("unused")
+    private static final String TAG = "DynamicGrid";
+
+    private DeviceProfile mProfile;
+    private float mMinWidth;
+    private float mMinHeight;
+
+    public static float dpiFromPx(int size, DisplayMetrics metrics){
+        float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+        return (size / densityRatio);
+    }
+    public static int pxFromDp(float size, DisplayMetrics metrics) {
+        return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                size, metrics));
+    }
+    public static int pxFromSp(float size, DisplayMetrics metrics) {
+        return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                size, metrics));
+    }
+
+    public DynamicGrid(Context context, Resources resources,
+                       int minWidthPx, int minHeightPx,
+                       int widthPx, int heightPx,
+                       int awPx, int ahPx) {
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        ArrayList<DeviceProfile> deviceProfiles =
+                new ArrayList<DeviceProfile>();
+        boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
+        // Our phone profiles include the bar sizes in each orientation
+        deviceProfiles.add(new DeviceProfile("Super Short Stubby",
+                255, 300,  2, 3,  48, 13, (hasAA ? 5 : 4), 48));
+        deviceProfiles.add(new DeviceProfile("Shorter Stubby",
+                255, 400,  3, 3,  48, 13, (hasAA ? 5 : 4), 48));
+        deviceProfiles.add(new DeviceProfile("Short Stubby",
+                275, 420,  3, 4,  48, 13, (hasAA ? 5 : 4), 48));
+        deviceProfiles.add(new DeviceProfile("Stubby",
+                255, 450,  3, 4,  48, 13, (hasAA ? 5 : 4), 48));
+        deviceProfiles.add(new DeviceProfile("Nexus S",
+                296, 491.33f,  4, 4,  48, 13, (hasAA ? 5 : 4), 48));
+        deviceProfiles.add(new DeviceProfile("Nexus 4",
+                359, 518,  4, 4,  60, 13, (hasAA ? 5 : 4), 56));
+        // The tablet profile is odd in that the landscape orientation
+        // also includes the nav bar on the side
+        deviceProfiles.add(new DeviceProfile("Nexus 7",
+                575, 904,  6, 6,  72, 14.4f,  7, 60));
+        // Larger tablet profiles always have system bars on the top & bottom
+        deviceProfiles.add(new DeviceProfile("Nexus 10",
+                727, 1207,  5, 8,  80, 14.4f,  9, 64));
+        /*
+        deviceProfiles.add(new DeviceProfile("Nexus 7",
+                600, 960,  5, 5,  72, 14.4f,  5, 60));
+        deviceProfiles.add(new DeviceProfile("Nexus 10",
+                800, 1280,  5, 5,  80, 14.4f, (hasAA ? 7 : 6), 64));
+         */
+        deviceProfiles.add(new DeviceProfile("20-inch Tablet",
+                1527, 2527,  7, 7,  100, 20,  7, 72));
+        mMinWidth = dpiFromPx(minWidthPx, dm);
+        mMinHeight = dpiFromPx(minHeightPx, dm);
+        mProfile = new DeviceProfile(context, deviceProfiles,
+                mMinWidth, mMinHeight,
+                widthPx, heightPx,
+                awPx, ahPx,
+                resources);
+    }
+
+    DeviceProfile getDeviceProfile() {
+        return mProfile;
+    }
+
+    public String toString() {
+        return "-------- DYNAMIC GRID ------- \n" +
+                "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
+                ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
+                " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
+                ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +
+                ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
+                ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
+    }
+}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
new file mode 100644
index 0000000..14760c7
--- /dev/null
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+class FastBitmapDrawable extends Drawable {
+    private Bitmap mBitmap;
+    private int mAlpha;
+    private int mWidth;
+    private int mHeight;
+    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+
+    FastBitmapDrawable(Bitmap b) {
+	mAlpha = 255;
+        mBitmap = b;
+        if (b != null) {
+            mWidth = mBitmap.getWidth();
+            mHeight = mBitmap.getHeight();
+        } else {
+            mWidth = mHeight = 0;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Rect r = getBounds();
+        // Draw the bitmap into the bounding rect
+        canvas.drawBitmap(mBitmap, null, r, mPaint);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mAlpha = alpha;
+        mPaint.setAlpha(alpha);
+    }
+
+    public void setFilterBitmap(boolean filterBitmap) {
+        mPaint.setFilterBitmap(filterBitmap);
+    }
+
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public int getMinimumWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        return mHeight;
+    }
+
+    public void setBitmap(Bitmap b) {
+        mBitmap = b;
+        if (b != null) {
+            mWidth = mBitmap.getWidth();
+            mHeight = mBitmap.getHeight();
+        } else {
+            mWidth = mHeight = 0;
+        }
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+}
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
new file mode 100644
index 0000000..78fdadd
--- /dev/null
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+
+/*
+ *  This is a helper class that listens to updates from the corresponding animation.
+ *  For the first two frames, it adjusts the current play time of the animation to
+ *  prevent jank at the beginning of the animation
+ */
+public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
+    implements ValueAnimator.AnimatorUpdateListener {
+    private static final boolean DEBUG = false;
+    private static final int MAX_DELAY = 1000;
+    private static final int IDEAL_FRAME_DURATION = 16;
+    private View mTarget;
+    private long mStartFrame;
+    private long mStartTime = -1;
+    private boolean mHandlingOnAnimationUpdate;
+    private boolean mAdjustedSecondFrameTime;
+
+    private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
+    private static long sGlobalFrameCounter;
+    private static boolean sVisible;
+
+    public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
+        mTarget = target;
+        animator.addUpdateListener(this);
+    }
+
+    public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
+        mTarget = target;
+        vpa.setListener(this);
+    }
+
+    // only used for ViewPropertyAnimators
+    public void onAnimationStart(Animator animation) {
+        final ValueAnimator va = (ValueAnimator) animation;
+        va.addUpdateListener(FirstFrameAnimatorHelper.this);
+        onAnimationUpdate(va);
+    }
+
+    public static void setIsVisible(boolean visible) {
+        sVisible = visible;
+    }
+
+    public static void initializeDrawListener(View view) {
+        if (sGlobalDrawListener != null) {
+            view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
+        }
+        sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
+                private long mTime = System.currentTimeMillis();
+                public void onDraw() {
+                    sGlobalFrameCounter++;
+                    if (DEBUG) {
+                        long newTime = System.currentTimeMillis();
+                        Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
+                        mTime = newTime;
+                    }
+                }
+            };
+        view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
+        sVisible = true;
+    }
+
+    public void onAnimationUpdate(final ValueAnimator animation) {
+        final long currentTime = System.currentTimeMillis();
+        if (mStartTime == -1) {
+            mStartFrame = sGlobalFrameCounter;
+            mStartTime = currentTime;
+        }
+
+        if (!mHandlingOnAnimationUpdate &&
+            sVisible &&
+            // If the current play time exceeds the duration, the animation
+            // will get finished, even if we call setCurrentPlayTime -- therefore
+            // don't adjust the animation in that case
+            animation.getCurrentPlayTime() < animation.getDuration()) {
+            mHandlingOnAnimationUpdate = true;
+            long frameNum = sGlobalFrameCounter - mStartFrame;
+            // If we haven't drawn our first frame, reset the time to t = 0
+            // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
+            // are no longer in the foreground and no frames are being rendered ever)
+            if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
+                // The first frame on animations doesn't always trigger an invalidate...
+                // force an invalidate here to make sure the animation continues to advance
+                mTarget.getRootView().invalidate();
+                animation.setCurrentPlayTime(0);
+
+            // For the second frame, if the first frame took more than 16ms,
+            // adjust the start time and pretend it took only 16ms anyway. This
+            // prevents a large jump in the animation due to an expensive first frame
+            } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+                       !mAdjustedSecondFrameTime &&
+                       currentTime > mStartTime + IDEAL_FRAME_DURATION) {
+                animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
+                mAdjustedSecondFrameTime = true;
+            } else {
+                if (frameNum > 1) {
+                    mTarget.post(new Runnable() {
+                            public void run() {
+                                animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
+                            }
+                        });
+                }
+                if (DEBUG) print(animation);
+            }
+            mHandlingOnAnimationUpdate = false;
+        } else {
+            if (DEBUG) print(animation);
+        }
+    }
+
+    public void print(ValueAnimator animation) {
+        float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
+        Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
+              "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
+              mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
+    }
+}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
new file mode 100644
index 0000000..4600985
--- /dev/null
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -0,0 +1,898 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.res.Configuration;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ScrollView;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * A keyboard listener we set on all the workspace icons.
+ */
+class IconKeyEventListener implements View.OnKeyListener {
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        return FocusHelper.handleIconKeyEvent(v, keyCode, event);
+    }
+}
+
+/**
+ * A keyboard listener we set on all the workspace icons.
+ */
+class FolderKeyEventListener implements View.OnKeyListener {
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
+    }
+}
+
+/**
+ * A keyboard listener we set on all the hotseat buttons.
+ */
+class HotseatIconKeyEventListener implements View.OnKeyListener {
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        final Configuration configuration = v.getResources().getConfiguration();
+        return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
+    }
+}
+
+/**
+ * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
+ * market icon and vice versa.
+ */
+class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
+    }
+}
+
+public class FocusHelper {
+    /**
+     * Private helper to get the parent TabHost in the view hiearchy.
+     */
+    private static TabHost findTabHostParent(View v) {
+        ViewParent p = v.getParent();
+        while (p != null && !(p instanceof TabHost)) {
+            p = p.getParent();
+        }
+        return (TabHost) p;
+    }
+
+    /**
+     * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
+     */
+    static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
+        final TabHost tabHost = findTabHostParent(v);
+        final ViewGroup contents = tabHost.getTabContentView();
+        final View shop = tabHost.findViewById(R.id.market_button);
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the shop button if we aren't on it
+                    if (v != shop) {
+                        shop.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the content view (down is handled by the tab key handler otherwise)
+                    if (v == shop) {
+                        contents.requestFocus();
+                        wasHandled = true;
+                    }
+                }
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Returns the Viewgroup containing page contents for the page at the index specified.
+     */
+    private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
+        ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
+        if (page instanceof CellLayout) {
+            // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
+            page = ((CellLayout) page).getShortcutsAndWidgets();
+        }
+        return page;
+    }
+
+    /**
+     * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
+     */
+    static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
+            KeyEvent e) {
+
+        final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
+        final PagedView container = (PagedView) parent.getParent();
+        final TabHost tabHost = findTabHostParent(container);
+        final TabWidget tabs = tabHost.getTabWidget();
+        final int widgetIndex = parent.indexOfChild(w);
+        final int widgetCount = parent.getChildCount();
+        final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
+        final int pageCount = container.getChildCount();
+        final int cellCountX = parent.getCellCountX();
+        final int cellCountY = parent.getCellCountY();
+        final int x = widgetIndex % cellCountX;
+        final int y = widgetIndex / cellCountX;
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        ViewGroup newParent = null;
+        // Now that we load items in the bg asynchronously, we can't just focus
+        // child siblings willy-nilly
+        View child = null;
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous widget or the last widget on the previous page
+                    if (widgetIndex > 0) {
+                        parent.getChildAt(widgetIndex - 1).requestFocus();
+                    } else {
+                        if (pageIndex > 0) {
+                            newParent = getAppsCustomizePage(container, pageIndex - 1);
+                            if (newParent != null) {
+                                child = newParent.getChildAt(newParent.getChildCount() - 1);
+                                if (child != null) child.requestFocus();
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next widget or the first widget on the next page
+                    if (widgetIndex < (widgetCount - 1)) {
+                        parent.getChildAt(widgetIndex + 1).requestFocus();
+                    } else {
+                        if (pageIndex < (pageCount - 1)) {
+                            newParent = getAppsCustomizePage(container, pageIndex + 1);
+                            if (newParent != null) {
+                                child = newParent.getChildAt(0);
+                                if (child != null) child.requestFocus();
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise select the tab bar
+                    if (y > 0) {
+                        int newWidgetIndex = ((y - 1) * cellCountX) + x;
+                        child = parent.getChildAt(newWidgetIndex);
+                        if (child != null) child.requestFocus();
+                    } else {
+                        tabs.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise do nothing
+                    if (y < (cellCountY - 1)) {
+                        int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
+                        child = parent.getChildAt(newWidgetIndex);
+                        if (child != null) child.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (handleKeyEvent) {
+                    // Simulate a click on the widget
+                    View.OnClickListener clickListener = (View.OnClickListener) container;
+                    clickListener.onClick(w);
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_UP:
+                if (handleKeyEvent) {
+                    // Select the first item on the previous page, or the first item on this page
+                    // if there is no previous page
+                    if (pageIndex > 0) {
+                        newParent = getAppsCustomizePage(container, pageIndex - 1);
+                        if (newParent != null) {
+                            child = newParent.getChildAt(0);
+                        }
+                    } else {
+                        child = parent.getChildAt(0);
+                    }
+                    if (child != null) child.requestFocus();
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+                if (handleKeyEvent) {
+                    // Select the first item on the next page, or the last item on this page
+                    // if there is no next page
+                    if (pageIndex < (pageCount - 1)) {
+                        newParent = getAppsCustomizePage(container, pageIndex + 1);
+                        if (newParent != null) {
+                            child = newParent.getChildAt(0);
+                        }
+                    } else {
+                        child = parent.getChildAt(widgetCount - 1);
+                    }
+                    if (child != null) child.requestFocus();
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                if (handleKeyEvent) {
+                    // Select the first item on this page
+                    child = parent.getChildAt(0);
+                    if (child != null) child.requestFocus();
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_END:
+                if (handleKeyEvent) {
+                    // Select the last item on this page
+                    parent.getChildAt(widgetCount - 1).requestFocus();
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Handles key events in a PageViewCellLayout containing PagedViewIcons.
+     */
+    static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+        ViewGroup parentLayout;
+        ViewGroup itemContainer;
+        int countX;
+        int countY;
+        if (v.getParent() instanceof ShortcutAndWidgetContainer) {
+            itemContainer = (ViewGroup) v.getParent();
+            parentLayout = (ViewGroup) itemContainer.getParent();
+            countX = ((CellLayout) parentLayout).getCountX();
+            countY = ((CellLayout) parentLayout).getCountY();
+        } else {
+            itemContainer = parentLayout = (ViewGroup) v.getParent();
+            countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
+            countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
+        }
+
+        // Note we have an extra parent because of the
+        // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
+        final PagedView container = (PagedView) parentLayout.getParent();
+        final TabHost tabHost = findTabHostParent(container);
+        final TabWidget tabs = tabHost.getTabWidget();
+        final int iconIndex = itemContainer.indexOfChild(v);
+        final int itemCount = itemContainer.getChildCount();
+        final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
+        final int pageCount = container.getChildCount();
+
+        final int x = iconIndex % countX;
+        final int y = iconIndex / countX;
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        ViewGroup newParent = null;
+        // Side pages do not always load synchronously, so check before focusing child siblings
+        // willy-nilly
+        View child = null;
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous icon or the last icon on the previous page
+                    if (iconIndex > 0) {
+                        itemContainer.getChildAt(iconIndex - 1).requestFocus();
+                    } else {
+                        if (pageIndex > 0) {
+                            newParent = getAppsCustomizePage(container, pageIndex - 1);
+                            if (newParent != null) {
+                                container.snapToPage(pageIndex - 1);
+                                child = newParent.getChildAt(newParent.getChildCount() - 1);
+                                if (child != null) child.requestFocus();
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next icon or the first icon on the next page
+                    if (iconIndex < (itemCount - 1)) {
+                        itemContainer.getChildAt(iconIndex + 1).requestFocus();
+                    } else {
+                        if (pageIndex < (pageCount - 1)) {
+                            newParent = getAppsCustomizePage(container, pageIndex + 1);
+                            if (newParent != null) {
+                                container.snapToPage(pageIndex + 1);
+                                child = newParent.getChildAt(0);
+                                if (child != null) child.requestFocus();
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise select the tab bar
+                    if (y > 0) {
+                        int newiconIndex = ((y - 1) * countX) + x;
+                        itemContainer.getChildAt(newiconIndex).requestFocus();
+                    } else {
+                        tabs.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise do nothing
+                    if (y < (countY - 1)) {
+                        int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
+                        itemContainer.getChildAt(newiconIndex).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (handleKeyEvent) {
+                    // Simulate a click on the icon
+                    View.OnClickListener clickListener = (View.OnClickListener) container;
+                    clickListener.onClick(v);
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_UP:
+                if (handleKeyEvent) {
+                    // Select the first icon on the previous page, or the first icon on this page
+                    // if there is no previous page
+                    if (pageIndex > 0) {
+                        newParent = getAppsCustomizePage(container, pageIndex - 1);
+                        if (newParent != null) {
+                            container.snapToPage(pageIndex - 1);
+                            child = newParent.getChildAt(0);
+                            if (child != null) child.requestFocus();
+                        }
+                    } else {
+                        itemContainer.getChildAt(0).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+                if (handleKeyEvent) {
+                    // Select the first icon on the next page, or the last icon on this page
+                    // if there is no next page
+                    if (pageIndex < (pageCount - 1)) {
+                        newParent = getAppsCustomizePage(container, pageIndex + 1);
+                        if (newParent != null) {
+                            container.snapToPage(pageIndex + 1);
+                            child = newParent.getChildAt(0);
+                            if (child != null) child.requestFocus();
+                        }
+                    } else {
+                        itemContainer.getChildAt(itemCount - 1).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                if (handleKeyEvent) {
+                    // Select the first icon on this page
+                    itemContainer.getChildAt(0).requestFocus();
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_END:
+                if (handleKeyEvent) {
+                    // Select the last icon on this page
+                    itemContainer.getChildAt(itemCount - 1).requestFocus();
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Handles key events in the tab widget.
+     */
+    static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
+        if (!LauncherAppState.getInstance().isScreenLarge()) return false;
+
+        final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
+        final TabHost tabHost = findTabHostParent(parent);
+        final ViewGroup contents = tabHost.getTabContentView();
+        final int tabCount = parent.getTabCount();
+        final int tabIndex = parent.getChildTabIndex(v);
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous tab
+                    if (tabIndex > 0) {
+                        parent.getChildTabViewAt(tabIndex - 1).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next tab, or if the last tab has a focus right id, select that
+                    if (tabIndex < (tabCount - 1)) {
+                        parent.getChildTabViewAt(tabIndex + 1).requestFocus();
+                    } else {
+                        if (v.getNextFocusRightId() != View.NO_ID) {
+                            tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                // Do nothing
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the content view
+                    contents.requestFocus();
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Handles key events in the workspace hotseat (bottom of the screen).
+     */
+    static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
+        final ViewGroup parent = (ViewGroup) v.getParent();
+        final ViewGroup launcher = (ViewGroup) parent.getParent();
+        final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
+        final int buttonIndex = parent.indexOfChild(v);
+        final int buttonCount = parent.getChildCount();
+        final int pageIndex = workspace.getCurrentPage();
+
+        // NOTE: currently we don't special case for the phone UI in different
+        // orientations, even though the hotseat is on the side in landscape mode.  This
+        // is to ensure that accessibility consistency is maintained across rotations.
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous button, otherwise snap to the previous page
+                    if (buttonIndex > 0) {
+                        parent.getChildAt(buttonIndex - 1).requestFocus();
+                    } else {
+                        workspace.snapToPage(pageIndex - 1);
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next button, otherwise snap to the next page
+                    if (buttonIndex < (buttonCount - 1)) {
+                        parent.getChildAt(buttonIndex + 1).requestFocus();
+                    } else {
+                        workspace.snapToPage(pageIndex + 1);
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the first bubble text view in the current page of the workspace
+                    final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
+                    final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
+                    final View newIcon = getIconInDirection(layout, children, -1, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        workspace.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                // Do nothing
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Private helper method to get the CellLayoutChildren given a CellLayout index.
+     */
+    private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
+            ViewGroup container, int i) {
+        ViewGroup parent = (ViewGroup) container.getChildAt(i);
+        return (ShortcutAndWidgetContainer) parent.getChildAt(0);
+    }
+
+    /**
+     * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
+     * from top left to bottom right.
+     */
+    private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
+            ViewGroup parent) {
+        // First we order each the CellLayout children by their x,y coordinates
+        final int cellCountX = layout.getCountX();
+        final int count = parent.getChildCount();
+        ArrayList<View> views = new ArrayList<View>();
+        for (int j = 0; j < count; ++j) {
+            views.add(parent.getChildAt(j));
+        }
+        Collections.sort(views, new Comparator<View>() {
+            @Override
+            public int compare(View lhs, View rhs) {
+                CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
+                CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
+                int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
+                int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
+                return lvIndex - rvIndex;
+            }
+        });
+        return views;
+    }
+    /**
+     * Private helper method to find the index of the next BubbleTextView or FolderIcon in the 
+     * direction delta.
+     * 
+     * @param delta either -1 or 1 depending on the direction we want to search
+     */
+    private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
+        // Then we find the next BubbleTextView offset by delta from i
+        final int count = views.size();
+        int newI = i + delta;
+        while (0 <= newI && newI < count) {
+            View newV = views.get(newI);
+            if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
+                return newV;
+            }
+            newI += delta;
+        }
+        return null;
+    }
+    private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
+            int delta) {
+        final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+        return findIndexOfIcon(views, i, delta);
+    }
+    private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
+            int delta) {
+        final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+        return findIndexOfIcon(views, views.indexOf(v), delta);
+    }
+    /**
+     * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction 
+     * delta on the next line.
+     * 
+     * @param delta either -1 or 1 depending on the line and direction we want to search
+     */
+    private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
+            int lineDelta) {
+        final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+        final int cellCountY = layout.getCountY();
+        final int row = lp.cellY;
+        final int newRow = row + lineDelta;
+        if (0 <= newRow && newRow < cellCountY) {
+            float closestDistance = Float.MAX_VALUE;
+            int closestIndex = -1;
+            int index = views.indexOf(v);
+            int endIndex = (lineDelta < 0) ? -1 : views.size();
+            while (index != endIndex) {
+                View newV = views.get(index);
+                CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
+                boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
+                if (satisfiesRow &&
+                        (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
+                    float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
+                            Math.pow(tmpLp.cellY - lp.cellY, 2));
+                    if (tmpDistance < closestDistance) {
+                        closestIndex = index;
+                        closestDistance = tmpDistance;
+                    }
+                }
+                if (index <= endIndex) {
+                    ++index;
+                } else {
+                    --index;
+                }
+            }
+            if (closestIndex > -1) {
+                return views.get(closestIndex);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Handles key events in a Workspace containing.
+     */
+    static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
+        ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
+        final CellLayout layout = (CellLayout) parent.getParent();
+        final Workspace workspace = (Workspace) layout.getParent();
+        final ViewGroup launcher = (ViewGroup) workspace.getParent();
+        final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
+        final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
+        int pageIndex = workspace.indexOfChild(layout);
+        int pageCount = workspace.getChildCount();
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous icon or the last icon on the previous page if possible
+                    View newIcon = getIconInDirection(layout, parent, v, -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        if (pageIndex > 0) {
+                            parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                            newIcon = getIconInDirection(layout, parent,
+                                    parent.getChildCount(), -1);
+                            if (newIcon != null) {
+                                newIcon.requestFocus();
+                            } else {
+                                // Snap to the previous page
+                                workspace.snapToPage(pageIndex - 1);
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next icon or the first icon on the next page if possible
+                    View newIcon = getIconInDirection(layout, parent, v, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        if (pageIndex < (pageCount - 1)) {
+                            parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                            newIcon = getIconInDirection(layout, parent, -1, 1);
+                            if (newIcon != null) {
+                                newIcon.requestFocus();
+                            } else {
+                                // Snap to the next page
+                                workspace.snapToPage(pageIndex + 1);
+                            }
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous line, otherwise select the tab bar
+                    View newIcon = getClosestIconOnLine(layout, parent, v, -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                        wasHandled = true;
+                    } else {
+                        tabs.requestFocus();
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the next line, otherwise select the button bar
+                    View newIcon = getClosestIconOnLine(layout, parent, v, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                        wasHandled = true;
+                    } else if (hotseat != null) {
+                        hotseat.requestFocus();
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_PAGE_UP:
+                if (handleKeyEvent) {
+                    // Select the first icon on the previous page or the first icon on this page
+                    // if there is no previous page
+                    if (pageIndex > 0) {
+                        parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                        View newIcon = getIconInDirection(layout, parent, -1, 1);
+                        if (newIcon != null) {
+                            newIcon.requestFocus();
+                        } else {
+                            // Snap to the previous page
+                            workspace.snapToPage(pageIndex - 1);
+                        }
+                    } else {
+                        View newIcon = getIconInDirection(layout, parent, -1, 1);
+                        if (newIcon != null) {
+                            newIcon.requestFocus();
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+                if (handleKeyEvent) {
+                    // Select the first icon on the next page or the last icon on this page
+                    // if there is no previous page
+                    if (pageIndex < (pageCount - 1)) {
+                        parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                        View newIcon = getIconInDirection(layout, parent, -1, 1);
+                        if (newIcon != null) {
+                            newIcon.requestFocus();
+                        } else {
+                            // Snap to the next page
+                            workspace.snapToPage(pageIndex + 1);
+                        }
+                    } else {
+                        View newIcon = getIconInDirection(layout, parent,
+                                parent.getChildCount(), -1);
+                        if (newIcon != null) {
+                            newIcon.requestFocus();
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                if (handleKeyEvent) {
+                    // Select the first icon on this page
+                    View newIcon = getIconInDirection(layout, parent, -1, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_END:
+                if (handleKeyEvent) {
+                    // Select the last icon on this page
+                    View newIcon = getIconInDirection(layout, parent,
+                            parent.getChildCount(), -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Handles key events for items in a Folder.
+     */
+    static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
+        ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
+        final CellLayout layout = (CellLayout) parent.getParent();
+        final ScrollView scrollView = (ScrollView) layout.getParent();
+        final Folder folder = (Folder) scrollView.getParent();
+        View title = folder.mFolderName;
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous icon
+                    View newIcon = getIconInDirection(layout, parent, v, -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next icon
+                    View newIcon = getIconInDirection(layout, parent, v, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        title.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous line
+                    View newIcon = getClosestIconOnLine(layout, parent, v, -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the next line
+                    View newIcon = getClosestIconOnLine(layout, parent, v, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        title.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                if (handleKeyEvent) {
+                    // Select the first icon on this page
+                    View newIcon = getIconInDirection(layout, parent, -1, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_END:
+                if (handleKeyEvent) {
+                    // Select the last icon on this page
+                    View newIcon = getIconInDirection(layout, parent,
+                            parent.getChildCount(), -1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+}
diff --git a/src/com/android/launcher3/FocusOnlyTabWidget.java b/src/com/android/launcher3/FocusOnlyTabWidget.java
new file mode 100644
index 0000000..08fc311
--- /dev/null
+++ b/src/com/android/launcher3/FocusOnlyTabWidget.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TabWidget;
+
+public class FocusOnlyTabWidget extends TabWidget {
+    public FocusOnlyTabWidget(Context context) {
+        super(context);
+    }
+
+    public FocusOnlyTabWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public View getSelectedTab() {
+        final int count = getTabCount();
+        for (int i = 0; i < count; ++i) {
+            View v = getChildTabViewAt(i);
+            if (v.isSelected()) {
+                return v;
+            }
+        }
+        return null;
+    }
+
+    public int getChildTabIndex(View v) {
+        final int tabCount = getTabCount();
+        for (int i = 0; i < tabCount; ++i) {
+            if (getChildTabViewAt(i) == v) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public void setCurrentTabToFocusedTab() {
+        View tab = null;
+        int index = -1;
+        final int count = getTabCount();
+        for (int i = 0; i < count; ++i) {
+            View v = getChildTabViewAt(i);
+            if (v.hasFocus()) {
+                tab = v;
+                index = i;
+                break;
+            }
+        }
+        if (index > -1) {
+            super.setCurrentTab(index);
+            super.onFocusChange(tab, true);
+        }
+    }
+    public void superOnFocusChange(View v, boolean hasFocus) {
+        super.onFocusChange(v, hasFocus);
+    }
+
+    @Override
+    public void onFocusChange(android.view.View v, boolean hasFocus) {
+        if (v == this && hasFocus && getTabCount() > 0) {
+            getSelectedTab().requestFocus();
+            return;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
new file mode 100644
index 0000000..5231aac
--- /dev/null
+++ b/src/com/android/launcher3/Folder.java
@@ -0,0 +1,1226 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.support.v4.widget.AutoScrollHelper;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.Spannable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.launcher3.FolderInfo.FolderListener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Represents a set of icons chosen by the user or generated by the system.
+ */
+public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
+        View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
+        View.OnFocusChangeListener {
+    private static final String TAG = "Launcher.Folder";
+
+    protected DragController mDragController;
+    protected Launcher mLauncher;
+    protected FolderInfo mInfo;
+
+    static final int STATE_NONE = -1;
+    static final int STATE_SMALL = 0;
+    static final int STATE_ANIMATING = 1;
+    static final int STATE_OPEN = 2;
+
+    private int mExpandDuration;
+    protected CellLayout mContent;
+    private ScrollView mScrollView;
+    private final LayoutInflater mInflater;
+    private final IconCache mIconCache;
+    private int mState = STATE_NONE;
+    private static final int REORDER_ANIMATION_DURATION = 230;
+    private static final int REORDER_DELAY = 250;
+    private static final int ON_EXIT_CLOSE_DELAY = 800;
+    private boolean mRearrangeOnClose = false;
+    private FolderIcon mFolderIcon;
+    private int mMaxCountX;
+    private int mMaxCountY;
+    private int mMaxNumItems;
+    private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+    private Drawable mIconDrawable;
+    boolean mItemsInvalidated = false;
+    private ShortcutInfo mCurrentDragInfo;
+    private View mCurrentDragView;
+    boolean mSuppressOnAdd = false;
+    private int[] mTargetCell = new int[2];
+    private int[] mPreviousTargetCell = new int[2];
+    private int[] mEmptyCell = new int[2];
+    private Alarm mReorderAlarm = new Alarm();
+    private Alarm mOnExitAlarm = new Alarm();
+    private int mFolderNameHeight;
+    private Rect mTempRect = new Rect();
+    private boolean mDragInProgress = false;
+    private boolean mDeleteFolderOnDropCompleted = false;
+    private boolean mSuppressFolderDeletion = false;
+    private boolean mItemAddedBackToSelfViaIcon = false;
+    FolderEditText mFolderName;
+    private float mFolderIconPivotX;
+    private float mFolderIconPivotY;
+
+    private boolean mIsEditingName = false;
+    private InputMethodManager mInputMethodManager;
+
+    private static String sDefaultFolderName;
+    private static String sHintText;
+
+    private int DRAG_MODE_NONE = 0;
+    private int DRAG_MODE_REORDER = 1;
+    private int mDragMode = DRAG_MODE_NONE;
+
+    private boolean mDestroyed;
+
+    private AutoScrollHelper mAutoScrollHelper;
+
+    private Runnable mDeferredAction;
+    private boolean mDeferDropAfterUninstall;
+    private boolean mUninstallSuccessful;
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attribtues set containing the Workspace's customization values.
+     */
+    public Folder(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        setAlwaysDrawnWithCacheEnabled(false);
+        mInflater = LayoutInflater.from(context);
+        mIconCache = app.getIconCache();
+
+        Resources res = getResources();
+        mMaxCountX = (int) grid.numColumns;
+        mMaxCountY = (int) grid.numRows;
+        mMaxNumItems = mMaxCountX * mMaxCountY;
+
+        mInputMethodManager = (InputMethodManager)
+                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+        mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
+
+        if (sDefaultFolderName == null) {
+            sDefaultFolderName = res.getString(R.string.folder_name);
+        }
+        if (sHintText == null) {
+            sHintText = res.getString(R.string.folder_hint_text);
+        }
+        mLauncher = (Launcher) context;
+        // We need this view to be focusable in touch mode so that when text editing of the folder
+        // name is complete, we have something to focus on, thus hiding the cursor and giving
+        // reliable behvior when clicking the text field (since it will always gain focus on click).
+        setFocusableInTouchMode(true);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mScrollView = (ScrollView) findViewById(R.id.scroll_view);
+        mContent = (CellLayout) findViewById(R.id.folder_content);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+        mContent.setGridSize(0, 0);
+        mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+        mContent.setInvertIfRtl(true);
+        mFolderName = (FolderEditText) findViewById(R.id.folder_name);
+        mFolderName.setFolder(this);
+        mFolderName.setOnFocusChangeListener(this);
+
+        // We find out how tall the text view wants to be (it is set to wrap_content), so that
+        // we can allocate the appropriate amount of space for it.
+        int measureSpec = MeasureSpec.UNSPECIFIED;
+        mFolderName.measure(measureSpec, measureSpec);
+        mFolderNameHeight = mFolderName.getMeasuredHeight();
+
+        // We disable action mode for now since it messes up the view on phones
+        mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
+        mFolderName.setOnEditorActionListener(this);
+        mFolderName.setSelectAllOnFocus(true);
+        mFolderName.setInputType(mFolderName.getInputType() |
+                InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
+    }
+
+    private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return false;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return false;
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            return false;
+        }
+    };
+
+    public void onClick(View v) {
+        Object tag = v.getTag();
+        if (tag instanceof ShortcutInfo) {
+            mLauncher.onClick(v);
+        }
+    }
+
+    public boolean onLongClick(View v) {
+        // Return if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return true;
+
+        Object tag = v.getTag();
+        if (tag instanceof ShortcutInfo) {
+            ShortcutInfo item = (ShortcutInfo) tag;
+            if (!v.isInTouchMode()) {
+                return false;
+            }
+
+            mLauncher.dismissFolderCling(null);
+
+            mLauncher.getWorkspace().onDragStartedWithItem(v);
+            mLauncher.getWorkspace().beginDragShared(v, this);
+            mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
+
+            mCurrentDragInfo = item;
+            mEmptyCell[0] = item.cellX;
+            mEmptyCell[1] = item.cellY;
+            mCurrentDragView = v;
+
+            mContent.removeView(mCurrentDragView);
+            mInfo.remove(mCurrentDragInfo);
+            mDragInProgress = true;
+            mItemAddedBackToSelfViaIcon = false;
+        }
+        return true;
+    }
+
+    public boolean isEditingName() {
+        return mIsEditingName;
+    }
+
+    public void startEditingFolderName() {
+        mFolderName.setHint("");
+        mIsEditingName = true;
+    }
+
+    public void dismissEditingName() {
+        mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        doneEditingFolderName(true);
+    }
+
+    public void doneEditingFolderName(boolean commit) {
+        mFolderName.setHint(sHintText);
+        // Convert to a string here to ensure that no other state associated with the text field
+        // gets saved.
+        String newTitle = mFolderName.getText().toString();
+        mInfo.setTitle(newTitle);
+        LauncherModel.updateItemInDatabase(mLauncher, mInfo);
+
+        if (commit) {
+            sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                    String.format(getContext().getString(R.string.folder_renamed), newTitle));
+        }
+        // In order to clear the focus from the text field, we set the focus on ourself. This
+        // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
+        requestFocus();
+
+        Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
+        mIsEditingName = false;
+    }
+
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            dismissEditingName();
+            return true;
+        }
+        return false;
+    }
+
+    public View getEditTextRegion() {
+        return mFolderName;
+    }
+
+    public Drawable getDragDrawable() {
+        return mIconDrawable;
+    }
+
+    /**
+     * We need to handle touch events to prevent them from falling through to the workspace below.
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return true;
+    }
+
+    public void setDragController(DragController dragController) {
+        mDragController = dragController;
+    }
+
+    void setFolderIcon(FolderIcon icon) {
+        mFolderIcon = icon;
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // When the folder gets focus, we don't want to announce the list of items.
+        return true;
+    }
+
+    /**
+     * @return the FolderInfo object associated with this folder
+     */
+    FolderInfo getInfo() {
+        return mInfo;
+    }
+
+    private class GridComparator implements Comparator<ShortcutInfo> {
+        int mNumCols;
+        public GridComparator(int numCols) {
+            mNumCols = numCols;
+        }
+
+        @Override
+        public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
+            int lhIndex = lhs.cellY * mNumCols + lhs.cellX;
+            int rhIndex = rhs.cellY * mNumCols + rhs.cellX;
+            return (lhIndex - rhIndex);
+        }
+    }
+
+    private void placeInReadingOrder(ArrayList<ShortcutInfo> items) {
+        int maxX = 0;
+        int count = items.size();
+        for (int i = 0; i < count; i++) {
+            ShortcutInfo item = items.get(i);
+            if (item.cellX > maxX) {
+                maxX = item.cellX;
+            }
+        }
+
+        GridComparator gridComparator = new GridComparator(maxX + 1);
+        Collections.sort(items, gridComparator);
+        final int countX = mContent.getCountX();
+        for (int i = 0; i < count; i++) {
+            int x = i % countX;
+            int y = i / countX;
+            ShortcutInfo item = items.get(i);
+            item.cellX = x;
+            item.cellY = y;
+        }
+    }
+
+    void bind(FolderInfo info) {
+        mInfo = info;
+        ArrayList<ShortcutInfo> children = info.contents;
+        ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
+        setupContentForNumItems(children.size());
+        placeInReadingOrder(children);
+        int count = 0;
+        for (int i = 0; i < children.size(); i++) {
+            ShortcutInfo child = (ShortcutInfo) children.get(i);
+            if (!createAndAddShortcut(child)) {
+                overflow.add(child);
+            } else {
+                count++;
+            }
+        }
+
+        // We rearrange the items in case there are any empty gaps
+        setupContentForNumItems(count);
+
+        // If our folder has too many items we prune them from the list. This is an issue 
+        // when upgrading from the old Folders implementation which could contain an unlimited
+        // number of items.
+        for (ShortcutInfo item: overflow) {
+            mInfo.remove(item);
+            LauncherModel.deleteItemFromDatabase(mLauncher, item);
+        }
+
+        mItemsInvalidated = true;
+        updateTextViewFocus();
+        mInfo.addListener(this);
+
+        if (!sDefaultFolderName.contentEquals(mInfo.title)) {
+            mFolderName.setText(mInfo.title);
+        } else {
+            mFolderName.setText("");
+        }
+        updateItemLocationsInDatabase();
+    }
+
+    /**
+     * Creates a new UserFolder, inflated from R.layout.user_folder.
+     *
+     * @param context The application's context.
+     *
+     * @return A new UserFolder.
+     */
+    static Folder fromXml(Context context) {
+        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
+    }
+
+    /**
+     * This method is intended to make the UserFolder to be visually identical in size and position
+     * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
+     */
+    private void positionAndSizeAsIcon() {
+        if (!(getParent() instanceof DragLayer)) return;
+        setScaleX(0.8f);
+        setScaleY(0.8f);
+        setAlpha(0f);
+        mState = STATE_SMALL;
+    }
+
+    public void animateOpen() {
+        positionAndSizeAsIcon();
+
+        if (!(getParent() instanceof DragLayer)) return;
+        centerAboutIcon();
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+        final ObjectAnimator oa =
+            LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        String.format(getContext().getString(R.string.folder_opened),
+                        mContent.getCountX(), mContent.getCountY()));
+                mState = STATE_ANIMATING;
+            }
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mState = STATE_OPEN;
+                setLayerType(LAYER_TYPE_NONE, null);
+                Cling cling = mLauncher.showFirstRunFoldersCling();
+                if (cling != null) {
+                    cling.bringScrimToFront();
+                    bringToFront();
+                    cling.bringToFront();
+                }
+                setFocusOnFirstChild();
+            }
+        });
+        oa.setDuration(mExpandDuration);
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+        oa.start();
+    }
+
+    private void sendCustomAccessibilityEvent(int type, String text) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (accessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            onInitializeAccessibilityEvent(event);
+            event.getText().add(text);
+            accessibilityManager.sendAccessibilityEvent(event);
+        }
+    }
+
+    private void setFocusOnFirstChild() {
+        View firstChild = mContent.getChildAt(0, 0);
+        if (firstChild != null) {
+            firstChild.requestFocus();
+        }
+    }
+
+    public void animateClosed() {
+        if (!(getParent() instanceof DragLayer)) return;
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
+        final ObjectAnimator oa =
+                LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                onCloseComplete();
+                setLayerType(LAYER_TYPE_NONE, null);
+                mState = STATE_SMALL;
+            }
+            @Override
+            public void onAnimationStart(Animator animation) {
+                sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        getContext().getString(R.string.folder_closed));
+                mState = STATE_ANIMATING;
+            }
+        });
+        oa.setDuration(mExpandDuration);
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+        oa.start();
+    }
+
+    public boolean acceptDrop(DragObject d) {
+        final ItemInfo item = (ItemInfo) d.dragInfo;
+        final int itemType = item.itemType;
+        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                    !isFull());
+    }
+
+    protected boolean findAndSetEmptyCells(ShortcutInfo item) {
+        int[] emptyCell = new int[2];
+        if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
+            item.cellX = emptyCell[0];
+            item.cellY = emptyCell[1];
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    protected boolean createAndAddShortcut(ShortcutInfo item) {
+        final BubbleTextView textView =
+            (BubbleTextView) mInflater.inflate(R.layout.application, this, false);
+        textView.setCompoundDrawablesWithIntrinsicBounds(null,
+                new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
+        textView.setText(item.title);
+        textView.setTag(item);
+        textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
+        textView.setShadowsEnabled(false);
+
+        textView.setOnClickListener(this);
+        textView.setOnLongClickListener(this);
+
+        // We need to check here to verify that the given item's location isn't already occupied
+        // by another item.
+        if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
+                || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
+            // This shouldn't happen, log it. 
+            Log.e(TAG, "Folder order not properly persisted during bind");
+            if (!findAndSetEmptyCells(item)) {
+                return false;
+            }
+        }
+
+        CellLayout.LayoutParams lp =
+            new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
+        boolean insert = false;
+        textView.setOnKeyListener(new FolderKeyEventListener());
+        mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
+        return true;
+    }
+
+    public void onDragEnter(DragObject d) {
+        mPreviousTargetCell[0] = -1;
+        mPreviousTargetCell[1] = -1;
+        mOnExitAlarm.cancelAlarm();
+    }
+
+    OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
+        public void onAlarm(Alarm alarm) {
+            realTimeReorder(mEmptyCell, mTargetCell);
+        }
+    };
+
+    boolean readingOrderGreaterThan(int[] v1, int[] v2) {
+        if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void realTimeReorder(int[] empty, int[] target) {
+        boolean wrap;
+        int startX;
+        int endX;
+        int startY;
+        int delay = 0;
+        float delayAmount = 30;
+        if (readingOrderGreaterThan(target, empty)) {
+            wrap = empty[0] >= mContent.getCountX() - 1;
+            startY = wrap ? empty[1] + 1 : empty[1];
+            for (int y = startY; y <= target[1]; y++) {
+                startX = y == empty[1] ? empty[0] + 1 : 0;
+                endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
+                for (int x = startX; x <= endX; x++) {
+                    View v = mContent.getChildAt(x,y);
+                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
+                            REORDER_ANIMATION_DURATION, delay, true, true)) {
+                        empty[0] = x;
+                        empty[1] = y;
+                        delay += delayAmount;
+                        delayAmount *= 0.9;
+                    }
+                }
+            }
+        } else {
+            wrap = empty[0] == 0;
+            startY = wrap ? empty[1] - 1 : empty[1];
+            for (int y = startY; y >= target[1]; y--) {
+                startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
+                endX = y > target[1] ? 0 : target[0];
+                for (int x = startX; x >= endX; x--) {
+                    View v = mContent.getChildAt(x,y);
+                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
+                            REORDER_ANIMATION_DURATION, delay, true, true)) {
+                        empty[0] = x;
+                        empty[1] = y;
+                        delay += delayAmount;
+                        delayAmount *= 0.9;
+                    }
+                }
+            }
+        }
+    }
+
+    public boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    public void onDragOver(DragObject d) {
+        final DragView dragView = d.dragView;
+        final int scrollOffset = mScrollView.getScrollY();
+        final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
+        r[0] -= getPaddingLeft();
+        r[1] -= getPaddingTop();
+
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent translatedEv = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
+
+        if (!mAutoScrollHelper.isEnabled()) {
+            mAutoScrollHelper.setEnabled(true);
+        }
+
+        final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
+        translatedEv.recycle();
+
+        if (handled) {
+            mReorderAlarm.cancelAlarm();
+        } else {
+            mTargetCell = mContent.findNearestArea(
+                    (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
+            if (isLayoutRtl()) {
+                mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
+            }
+            if (mTargetCell[0] != mPreviousTargetCell[0]
+                    || mTargetCell[1] != mPreviousTargetCell[1]) {
+                mReorderAlarm.cancelAlarm();
+                mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
+                mReorderAlarm.setAlarm(REORDER_DELAY);
+                mPreviousTargetCell[0] = mTargetCell[0];
+                mPreviousTargetCell[1] = mTargetCell[1];
+                mDragMode = DRAG_MODE_REORDER;
+            } else {
+                mDragMode = DRAG_MODE_NONE;
+            }
+        }
+    }
+
+    // This is used to compute the visual center of the dragView. The idea is that
+    // the visual center represents the user's interpretation of where the item is, and hence
+    // is the appropriate point to use when determining drop location.
+    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+            DragView dragView, float[] recycle) {
+        float res[];
+        if (recycle == null) {
+            res = new float[2];
+        } else {
+            res = recycle;
+        }
+
+        // These represent the visual top and left of drag view if a dragRect was provided.
+        // If a dragRect was not provided, then they correspond to the actual view left and
+        // top, as the dragRect is in that case taken to be the entire dragView.
+        // R.dimen.dragViewOffsetY.
+        int left = x - xOffset;
+        int top = y - yOffset;
+
+        // In order to find the visual center, we shift by half the dragRect
+        res[0] = left + dragView.getDragRegion().width() / 2;
+        res[1] = top + dragView.getDragRegion().height() / 2;
+
+        return res;
+    }
+
+    OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
+        public void onAlarm(Alarm alarm) {
+            completeDragExit();
+        }
+    };
+
+    public void completeDragExit() {
+        mLauncher.closeFolder();
+        mCurrentDragInfo = null;
+        mCurrentDragView = null;
+        mSuppressOnAdd = false;
+        mRearrangeOnClose = true;
+    }
+
+    public void onDragExit(DragObject d) {
+        // Exiting folder; stop the auto scroller.
+        mAutoScrollHelper.setEnabled(false);
+        // We only close the folder if this is a true drag exit, ie. not because
+        // a drop has occurred above the folder.
+        if (!d.dragComplete) {
+            mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
+            mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
+        }
+        mReorderAlarm.cancelAlarm();
+        mDragMode = DRAG_MODE_NONE;
+    }
+
+    public void onDropCompleted(final View target, final DragObject d,
+            final boolean isFlingToDelete, final boolean success) {
+        if (mDeferDropAfterUninstall) {
+            Log.d(TAG, "Deferred handling drop because waiting for uninstall.");
+            mDeferredAction = new Runnable() {
+                    public void run() {
+                        onDropCompleted(target, d, isFlingToDelete, success);
+                        mDeferredAction = null;
+                    }
+                };
+            return;
+        }
+
+        boolean beingCalledAfterUninstall = mDeferredAction != null;
+        boolean successfulDrop =
+                success && (!beingCalledAfterUninstall || mUninstallSuccessful);
+
+        if (successfulDrop) {
+            if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
+                replaceFolderWithFinalItem();
+            }
+        } else {
+            setupContentForNumItems(getItemCount());
+            // The drag failed, we need to return the item to the folder
+            mFolderIcon.onDrop(d);
+        }
+
+        if (target != this) {
+            if (mOnExitAlarm.alarmPending()) {
+                mOnExitAlarm.cancelAlarm();
+                if (!successfulDrop) {
+                    mSuppressFolderDeletion = true;
+                }
+                completeDragExit();
+            }
+        }
+
+        mDeleteFolderOnDropCompleted = false;
+        mDragInProgress = false;
+        mItemAddedBackToSelfViaIcon = false;
+        mCurrentDragInfo = null;
+        mCurrentDragView = null;
+        mSuppressOnAdd = false;
+
+        // Reordering may have occured, and we need to save the new item locations. We do this once
+        // at the end to prevent unnecessary database operations.
+        updateItemLocationsInDatabaseBatch();
+    }
+
+    public void deferCompleteDropAfterUninstallActivity() {
+        mDeferDropAfterUninstall = true;
+    }
+
+    public void onUninstallActivityReturned(boolean success) {
+        mDeferDropAfterUninstall = false;
+        mUninstallSuccessful = success;
+        if (mDeferredAction != null) {
+            mDeferredAction.run();
+        }
+    }
+
+    @Override
+    public boolean supportsFlingToDelete() {
+        return true;
+    }
+
+    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+        // Do nothing
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // Do nothing
+    }
+
+    private void updateItemLocationsInDatabase() {
+        ArrayList<View> list = getItemsInReadingOrder();
+        for (int i = 0; i < list.size(); i++) {
+            View v = list.get(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
+                        info.cellX, info.cellY);
+        }
+    }
+
+    private void updateItemLocationsInDatabaseBatch() {
+        ArrayList<View> list = getItemsInReadingOrder();
+        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+        for (int i = 0; i < list.size(); i++) {
+            View v = list.get(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            items.add(info);
+        }
+
+        LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
+    }
+
+    public void addItemLocationsInDatabase() {
+        ArrayList<View> list = getItemsInReadingOrder();
+        for (int i = 0; i < list.size(); i++) {
+            View v = list.get(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0,
+                        info.cellX, info.cellY, false);
+        }
+    }
+
+    public void notifyDrop() {
+        if (mDragInProgress) {
+            mItemAddedBackToSelfViaIcon = true;
+        }
+    }
+
+    public boolean isDropEnabled() {
+        return true;
+    }
+
+    private void setupContentDimensions(int count) {
+        ArrayList<View> list = getItemsInReadingOrder();
+
+        int countX = mContent.getCountX();
+        int countY = mContent.getCountY();
+        boolean done = false;
+
+        while (!done) {
+            int oldCountX = countX;
+            int oldCountY = countY;
+            if (countX * countY < count) {
+                // Current grid is too small, expand it
+                if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
+                    countX++;
+                } else if (countY < mMaxCountY) {
+                    countY++;
+                }
+                if (countY == 0) countY++;
+            } else if ((countY - 1) * countX >= count && countY >= countX) {
+                countY = Math.max(0, countY - 1);
+            } else if ((countX - 1) * countY >= count) {
+                countX = Math.max(0, countX - 1);
+            }
+            done = countX == oldCountX && countY == oldCountY;
+        }
+        mContent.setGridSize(countX, countY);
+        arrangeChildren(list);
+    }
+
+    public boolean isFull() {
+        return getItemCount() >= mMaxNumItems;
+    }
+
+    private void centerAboutIcon() {
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+
+        DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
+        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+        int height = getFolderHeight();
+
+        float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
+        int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
+        int centeredLeft = centerX - width / 2;
+        int centeredTop = centerY - height / 2;
+        int currentPage = mLauncher.getWorkspace().getNextPage();
+        // In case the workspace is scrolling, we need to use the final scroll to compute
+        // the folders bounds.
+        mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage);
+        // We first fetch the currently visible CellLayoutChildren
+        CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage);
+        ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets();
+        Rect bounds = new Rect();
+        parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
+        // We reset the workspaces scroll
+        mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage);
+
+        // We need to bound the folder to the currently visible CellLayoutChildren
+        int left = Math.min(Math.max(bounds.left, centeredLeft),
+                bounds.left + bounds.width() - width);
+        int top = Math.min(Math.max(bounds.top, centeredTop),
+                bounds.top + bounds.height() - height);
+        if (grid.isPhone() && (grid.availableWidthPx - width) < grid.iconSizePx) {
+            // Center the folder if it is full (on phones only)
+            left = (grid.availableWidthPx - width) / 2;
+        } else if (width >= bounds.width()) {
+            // If the folder doesn't fit within the bounds, center it about the desired bounds
+            left = bounds.left + (bounds.width() - width) / 2;
+        }
+        if (height >= bounds.height()) {
+            top = bounds.top + (bounds.height() - height) / 2;
+        }
+
+        int folderPivotX = width / 2 + (centeredLeft - left);
+        int folderPivotY = height / 2 + (centeredTop - top);
+        setPivotX(folderPivotX);
+        setPivotY(folderPivotY);
+        mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
+                (1.0f * folderPivotX / width));
+        mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
+                (1.0f * folderPivotY / height));
+
+        lp.width = width;
+        lp.height = height;
+        lp.x = left;
+        lp.y = top;
+    }
+
+    float getPivotXForIconAnimation() {
+        return mFolderIconPivotX;
+    }
+    float getPivotYForIconAnimation() {
+        return mFolderIconPivotY;
+    }
+
+    private void setupContentForNumItems(int count) {
+        setupContentDimensions(count);
+
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        if (lp == null) {
+            lp = new DragLayer.LayoutParams(0, 0);
+            lp.customPosition = true;
+            setLayoutParams(lp);
+        }
+        centerAboutIcon();
+    }
+
+    private int getContentAreaHeight() {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+        int maxContentAreaHeight = grid.availableHeightPx -
+                4 * grid.edgeMarginPx -
+                workspacePadding.top - workspacePadding.bottom -
+                getPaddingTop() - getPaddingBottom() -
+                mFolderNameHeight;
+        return Math.min(maxContentAreaHeight,
+                mContent.getDesiredHeight());
+    }
+
+    private int getFolderHeight() {
+        int height = getPaddingTop() + getPaddingBottom()
+                + getContentAreaHeight() + mFolderNameHeight;
+        return height;
+    }
+
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+        int height = getFolderHeight();
+        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(),
+                MeasureSpec.EXACTLY);
+        int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
+                MeasureSpec.EXACTLY);
+        mContent.setFixedSize(mContent.getDesiredWidth(), mContent.getDesiredHeight());
+        mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
+        mFolderName.measure(contentAreaWidthSpec,
+                MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
+        setMeasuredDimension(width, height);
+    }
+
+    private void arrangeChildren(ArrayList<View> list) {
+        int[] vacant = new int[2];
+        if (list == null) {
+            list = getItemsInReadingOrder();
+        }
+        mContent.removeAllViews();
+
+        for (int i = 0; i < list.size(); i++) {
+            View v = list.get(i);
+            mContent.getVacantCell(vacant, 1, 1);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+            lp.cellX = vacant[0];
+            lp.cellY = vacant[1];
+            ItemInfo info = (ItemInfo) v.getTag();
+            if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
+                info.cellX = vacant[0];
+                info.cellY = vacant[1];
+                LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
+                        info.cellX, info.cellY);
+            }
+            boolean insert = false;
+            mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
+        }
+        mItemsInvalidated = true;
+    }
+
+    public int getItemCount() {
+        return mContent.getShortcutsAndWidgets().getChildCount();
+    }
+
+    public View getItemAt(int index) {
+        return mContent.getShortcutsAndWidgets().getChildAt(index);
+    }
+
+    private void onCloseComplete() {
+        DragLayer parent = (DragLayer) getParent();
+        if (parent != null) {
+            parent.removeView(this);
+        }
+        mDragController.removeDropTarget((DropTarget) this);
+        clearFocus();
+        mFolderIcon.requestFocus();
+
+        if (mRearrangeOnClose) {
+            setupContentForNumItems(getItemCount());
+            mRearrangeOnClose = false;
+        }
+        if (getItemCount() <= 1) {
+            if (!mDragInProgress && !mSuppressFolderDeletion) {
+                replaceFolderWithFinalItem();
+            } else if (mDragInProgress) {
+                mDeleteFolderOnDropCompleted = true;
+            }
+        }
+        mSuppressFolderDeletion = false;
+    }
+
+    private void replaceFolderWithFinalItem() {
+        // Add the last remaining child to the workspace in place of the folder
+        Runnable onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
+
+               View child = null;
+                // Move the item from the folder to the workspace, in the position of the folder
+                if (getItemCount() == 1) {
+                    ShortcutInfo finalItem = mInfo.contents.get(0);
+                    child = mLauncher.createShortcut(R.layout.application, cellLayout,
+                            finalItem);
+                    LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
+                            mInfo.screenId, mInfo.cellX, mInfo.cellY);
+                }
+                if (getItemCount() <= 1) {
+                    // Remove the folder
+                    LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
+                    cellLayout.removeView(mFolderIcon);
+                    if (mFolderIcon instanceof DropTarget) {
+                        mDragController.removeDropTarget((DropTarget) mFolderIcon);
+                    }
+                    mLauncher.removeFolder(mInfo);
+                }
+                // We add the child after removing the folder to prevent both from existing at
+                // the same time in the CellLayout.  We need to add the new item with addInScreenFromBind()
+                // to ensure that hotseat items are placed correctly.
+                if (child != null) {
+                    mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
+                            mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+                }
+            }
+        };
+        View finalChild = getItemAt(0);
+        if (finalChild != null) {
+            mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
+        }
+        mDestroyed = true;
+    }
+
+    boolean isDestroyed() {
+        return mDestroyed;
+    }
+
+    // This method keeps track of the last item in the folder for the purposes
+    // of keyboard focus
+    private void updateTextViewFocus() {
+        View lastChild = getItemAt(getItemCount() - 1);
+        getItemAt(getItemCount() - 1);
+        if (lastChild != null) {
+            mFolderName.setNextFocusDownId(lastChild.getId());
+            mFolderName.setNextFocusRightId(lastChild.getId());
+            mFolderName.setNextFocusLeftId(lastChild.getId());
+            mFolderName.setNextFocusUpId(lastChild.getId());
+        }
+    }
+
+    public void onDrop(DragObject d) {
+        ShortcutInfo item;
+        if (d.dragInfo instanceof AppInfo) {
+            // Came from all apps -- make a copy
+            item = ((AppInfo) d.dragInfo).makeShortcut();
+            item.spanX = 1;
+            item.spanY = 1;
+        } else {
+            item = (ShortcutInfo) d.dragInfo;
+        }
+        // Dragged from self onto self, currently this is the only path possible, however
+        // we keep this as a distinct code path.
+        if (item == mCurrentDragInfo) {
+            ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
+            si.cellX = lp.cellX = mEmptyCell[0];
+            si.cellX = lp.cellY = mEmptyCell[1];
+            mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
+            if (d.dragView.hasDrawn()) {
+                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView);
+            } else {
+                d.deferDragViewCleanupPostAnimation = false;
+                mCurrentDragView.setVisibility(VISIBLE);
+            }
+            mItemsInvalidated = true;
+            setupContentDimensions(getItemCount());
+            mSuppressOnAdd = true;
+        }
+        mInfo.add(item);
+    }
+
+    // This is used so the item doesn't immediately appear in the folder when added. In one case
+    // we need to create the illusion that the item isn't added back to the folder yet, to
+    // to correspond to the animation of the icon back into the folder. This is
+    public void hideItem(ShortcutInfo info) {
+        View v = getViewForInfo(info);
+        v.setVisibility(INVISIBLE);
+    }
+    public void showItem(ShortcutInfo info) {
+        View v = getViewForInfo(info);
+        v.setVisibility(VISIBLE);
+    }
+
+    public void onAdd(ShortcutInfo item) {
+        mItemsInvalidated = true;
+        // If the item was dropped onto this open folder, we have done the work associated
+        // with adding the item to the folder, as indicated by mSuppressOnAdd being set
+        if (mSuppressOnAdd) return;
+        if (!findAndSetEmptyCells(item)) {
+            // The current layout is full, can we expand it?
+            setupContentForNumItems(getItemCount() + 1);
+            findAndSetEmptyCells(item);
+        }
+        createAndAddShortcut(item);
+        LauncherModel.addOrMoveItemInDatabase(
+                mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
+    }
+
+    public void onRemove(ShortcutInfo item) {
+        mItemsInvalidated = true;
+        // If this item is being dragged from this open folder, we have already handled
+        // the work associated with removing the item, so we don't have to do anything here.
+        if (item == mCurrentDragInfo) return;
+        View v = getViewForInfo(item);
+        mContent.removeView(v);
+        if (mState == STATE_ANIMATING) {
+            mRearrangeOnClose = true;
+        } else {
+            setupContentForNumItems(getItemCount());
+        }
+        if (getItemCount() <= 1) {
+            replaceFolderWithFinalItem();
+        }
+    }
+
+    private View getViewForInfo(ShortcutInfo item) {
+        for (int j = 0; j < mContent.getCountY(); j++) {
+            for (int i = 0; i < mContent.getCountX(); i++) {
+                View v = mContent.getChildAt(i, j);
+                if (v.getTag() == item) {
+                    return v;
+                }
+            }
+        }
+        return null;
+    }
+
+    public void onItemsChanged() {
+        updateTextViewFocus();
+    }
+
+    public void onTitleChanged(CharSequence title) {
+    }
+
+    public ArrayList<View> getItemsInReadingOrder() {
+        if (mItemsInvalidated) {
+            mItemsInReadingOrder.clear();
+            for (int j = 0; j < mContent.getCountY(); j++) {
+                for (int i = 0; i < mContent.getCountX(); i++) {
+                    View v = mContent.getChildAt(i, j);
+                    if (v != null) {
+                        mItemsInReadingOrder.add(v);
+                    }
+                }
+            }
+            mItemsInvalidated = false;
+        }
+        return mItemsInReadingOrder;
+    }
+
+    public void getLocationInDragLayer(int[] loc) {
+        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+    }
+
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == mFolderName && hasFocus) {
+            startEditingFolderName();
+        }
+    }
+
+    @Override
+    public void getHitRectRelativeToDragLayer(Rect outRect) {
+        getHitRect(outRect);
+    }
+}
diff --git a/src/com/android/launcher3/FolderAutoScrollHelper.java b/src/com/android/launcher3/FolderAutoScrollHelper.java
new file mode 100644
index 0000000..40e8884
--- /dev/null
+++ b/src/com/android/launcher3/FolderAutoScrollHelper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.launcher3;
+
+import android.support.v4.widget.AutoScrollHelper;
+import android.widget.ScrollView;
+
+/**
+ * An implementation of {@link AutoScrollHelper} that knows how to scroll
+ * through a {@link Folder}.
+ */
+public class FolderAutoScrollHelper extends AutoScrollHelper {
+    private static final float MAX_SCROLL_VELOCITY = 1500f;
+
+    private final ScrollView mTarget;
+
+    public FolderAutoScrollHelper(ScrollView target) {
+        super(target);
+
+        mTarget = target;
+
+        setActivationDelay(0);
+        setEdgeType(EDGE_TYPE_INSIDE_EXTEND);
+        setExclusive(true);
+        setMaximumVelocity(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
+        setRampDownDuration(0);
+        setRampUpDuration(0);
+    }
+
+    @Override
+    public void scrollTargetBy(int deltaX, int deltaY) {
+        mTarget.scrollBy(deltaX, deltaY);
+    }
+
+    @Override
+    public boolean canTargetScrollHorizontally(int direction) {
+        // List do not scroll horizontally.
+        return false;
+    }
+
+    @Override
+    public boolean canTargetScrollVertically(int direction) {
+        return mTarget.canScrollVertically(direction);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
new file mode 100644
index 0000000..c311008
--- /dev/null
+++ b/src/com/android/launcher3/FolderEditText.java
@@ -0,0 +1,36 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+public class FolderEditText extends EditText {
+
+    private Folder mFolder;
+
+    public FolderEditText(Context context) {
+        super(context);
+    }
+
+    public FolderEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setFolder(Folder folder) {
+        mFolder = folder;
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        // Catch the back button on the soft keyboard so that we can just close the activity
+        if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
+            mFolder.doneEditingFolderName(true);
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
new file mode 100644
index 0000000..7e1e350
--- /dev/null
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -0,0 +1,682 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FolderInfo.FolderListener;
+
+import java.util.ArrayList;
+
+/**
+ * An icon that can appear on in the workspace representing an {@link UserFolder}.
+ */
+public class FolderIcon extends LinearLayout implements FolderListener {
+    private Launcher mLauncher;
+    private Folder mFolder;
+    private FolderInfo mInfo;
+    private static boolean sStaticValuesDirty = true;
+
+    private CheckLongPressHelper mLongPressHelper;
+
+    // The number of icons to display in the
+    private static final int NUM_ITEMS_IN_PREVIEW = 3;
+    private static final int CONSUMPTION_ANIMATION_DURATION = 100;
+    private static final int DROP_IN_ANIMATION_DURATION = 400;
+    private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
+    private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
+
+    // The degree to which the inner ring grows when accepting drop
+    private static final float INNER_RING_GROWTH_FACTOR = 0.15f;
+
+    // The degree to which the outer ring is scaled in its natural state
+    private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
+
+    // The amount of vertical spread between items in the stack [0...1]
+    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
+
+    // Flag as to whether or not to draw an outer ring. Currently none is designed.
+    public static final boolean HAS_OUTER_RING = true;
+
+    // The degree to which the item in the back of the stack is scaled [0...1]
+    // (0 means it's not scaled at all, 1 means it's scaled to nothing)
+    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+    public static Drawable sSharedFolderLeaveBehind = null;
+
+    private ImageView mPreviewBackground;
+    private BubbleTextView mFolderName;
+
+    FolderRingAnimator mFolderRingAnimator = null;
+
+    // These variables are all associated with the drawing of the preview; they are stored
+    // as member variables for shared usage and to avoid computation on each frame
+    private int mIntrinsicIconSize;
+    private float mBaselineIconScale;
+    private int mBaselineIconSize;
+    private int mAvailableSpaceInPreview;
+    private int mTotalWidth = -1;
+    private int mPreviewOffsetX;
+    private int mPreviewOffsetY;
+    private float mMaxPerspectiveShift;
+    boolean mAnimating = false;
+
+    private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+    private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+    private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
+
+    public FolderIcon(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public FolderIcon(Context context) {
+        super(context);
+        init();
+    }
+
+    private void init() {
+        mLongPressHelper = new CheckLongPressHelper(this);
+    }
+
+    public boolean isDropEnabled() {
+        final ViewGroup cellLayoutChildren = (ViewGroup) getParent();
+        final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent();
+        final Workspace workspace = (Workspace) cellLayout.getParent();
+        return !workspace.isSmall();
+    }
+
+    static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
+            FolderInfo folderInfo, IconCache iconCache) {
+        @SuppressWarnings("all") // suppress dead code warning
+        final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
+        if (error) {
+            throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
+                    "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
+                    "is dependent on this");
+        }
+
+        FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
+        icon.setClipToPadding(false);
+        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
+        icon.mFolderName.setText(folderInfo.title);
+        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        // Offset the preview background to center this view accordingly
+        LinearLayout.LayoutParams lp =
+                (LinearLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
+        lp.topMargin = grid.folderBackgroundOffset;
+        lp.width = grid.folderIconSizePx;
+        lp.height = grid.folderIconSizePx;
+
+        icon.setTag(folderInfo);
+        icon.setOnClickListener(launcher);
+        icon.mInfo = folderInfo;
+        icon.mLauncher = launcher;
+        icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
+                folderInfo.title));
+        Folder folder = Folder.fromXml(launcher);
+        folder.setDragController(launcher.getDragController());
+        folder.setFolderIcon(icon);
+        folder.bind(folderInfo);
+        icon.mFolder = folder;
+
+        icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
+        folderInfo.addListener(icon);
+
+        return icon;
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        sStaticValuesDirty = true;
+        return super.onSaveInstanceState();
+    }
+
+    public static class FolderRingAnimator {
+        public int mCellX;
+        public int mCellY;
+        private CellLayout mCellLayout;
+        public float mOuterRingSize;
+        public float mInnerRingSize;
+        public FolderIcon mFolderIcon = null;
+        public static Drawable sSharedOuterRingDrawable = null;
+        public static Drawable sSharedInnerRingDrawable = null;
+        public static int sPreviewSize = -1;
+        public static int sPreviewPadding = -1;
+
+        private ValueAnimator mAcceptAnimator;
+        private ValueAnimator mNeutralAnimator;
+
+        public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
+            mFolderIcon = folderIcon;
+            Resources res = launcher.getResources();
+
+            // We need to reload the static values when configuration changes in case they are
+            // different in another configuration
+            if (sStaticValuesDirty) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread "
+                            + Thread.currentThread());
+                }
+
+                LauncherAppState app = LauncherAppState.getInstance();
+                DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+                sPreviewSize = grid.folderIconSizePx;
+                sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
+                sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
+                sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip_holo);
+                sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest);
+                sStaticValuesDirty = false;
+            }
+        }
+
+        public void animateToAcceptState() {
+            if (mNeutralAnimator != null) {
+                mNeutralAnimator.cancel();
+            }
+            mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
+            mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+
+            final int previewSize = sPreviewSize;
+            mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float percent = (Float) animation.getAnimatedValue();
+                    mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize;
+                    mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize;
+                    if (mCellLayout != null) {
+                        mCellLayout.invalidate();
+                    }
+                }
+            });
+            mAcceptAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (mFolderIcon != null) {
+                        mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE);
+                    }
+                }
+            });
+            mAcceptAnimator.start();
+        }
+
+        public void animateToNaturalState() {
+            if (mAcceptAnimator != null) {
+                mAcceptAnimator.cancel();
+            }
+            mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
+            mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+
+            final int previewSize = sPreviewSize;
+            mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float percent = (Float) animation.getAnimatedValue();
+                    mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize;
+                    mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize;
+                    if (mCellLayout != null) {
+                        mCellLayout.invalidate();
+                    }
+                }
+            });
+            mNeutralAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mCellLayout != null) {
+                        mCellLayout.hideFolderAccept(FolderRingAnimator.this);
+                    }
+                    if (mFolderIcon != null) {
+                        mFolderIcon.mPreviewBackground.setVisibility(VISIBLE);
+                    }
+                }
+            });
+            mNeutralAnimator.start();
+        }
+
+        // Location is expressed in window coordinates
+        public void getCell(int[] loc) {
+            loc[0] = mCellX;
+            loc[1] = mCellY;
+        }
+
+        // Location is expressed in window coordinates
+        public void setCell(int x, int y) {
+            mCellX = x;
+            mCellY = y;
+        }
+
+        public void setCellLayout(CellLayout layout) {
+            mCellLayout = layout;
+        }
+
+        public float getOuterRingSize() {
+            return mOuterRingSize;
+        }
+
+        public float getInnerRingSize() {
+            return mInnerRingSize;
+        }
+    }
+
+    Folder getFolder() {
+        return mFolder;
+    }
+
+    FolderInfo getFolderInfo() {
+        return mInfo;
+    }
+
+    private boolean willAcceptItem(ItemInfo item) {
+        final int itemType = item.itemType;
+        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                !mFolder.isFull() && item != mInfo && !mInfo.opened);
+    }
+
+    public boolean acceptDrop(Object dragInfo) {
+        final ItemInfo item = (ItemInfo) dragInfo;
+        return !mFolder.isDestroyed() && willAcceptItem(item);
+    }
+
+    public void addItem(ShortcutInfo item) {
+        mInfo.add(item);
+    }
+
+    public void onDragEnter(Object dragInfo) {
+        if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return;
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        CellLayout layout = (CellLayout) getParent().getParent();
+        mFolderRingAnimator.setCell(lp.cellX, lp.cellY);
+        mFolderRingAnimator.setCellLayout(layout);
+        mFolderRingAnimator.animateToAcceptState();
+        layout.showFolderAccept(mFolderRingAnimator);
+    }
+
+    public void onDragOver(Object dragInfo) {
+    }
+
+    public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
+            final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
+            float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
+
+        // These correspond two the drawable and view that the icon was dropped _onto_
+        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+                destView.getMeasuredWidth());
+
+        // This will animate the first item from it's position as an icon into its
+        // position as the first item in the preview
+        animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
+        addItem(destInfo);
+
+        // This will animate the dragView (srcView) into the new folder
+        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
+    }
+
+    public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
+        Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
+        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 
+                finalView.getMeasuredWidth());
+
+        // This will animate the first item from it's position as an icon into its
+        // position as the first item in the preview
+        animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
+                onCompleteRunnable);
+    }
+
+    public void onDragExit(Object dragInfo) {
+        onDragExit();
+    }
+
+    public void onDragExit() {
+        mFolderRingAnimator.animateToNaturalState();
+    }
+
+    private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
+            float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable,
+            DragObject d) {
+        item.cellX = -1;
+        item.cellY = -1;
+
+        // Typically, the animateView corresponds to the DragView; however, if this is being done
+        // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
+        // will not have a view to animate
+        if (animateView != null) {
+            DragLayer dragLayer = mLauncher.getDragLayer();
+            Rect from = new Rect();
+            dragLayer.getViewRectRelativeToSelf(animateView, from);
+            Rect to = finalRect;
+            if (to == null) {
+                to = new Rect();
+                Workspace workspace = mLauncher.getWorkspace();
+                // Set cellLayout and this to it's final state to compute final animation locations
+                workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
+                float scaleX = getScaleX();
+                float scaleY = getScaleY();
+                setScaleX(1.0f);
+                setScaleY(1.0f);
+                scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
+                // Finished computing final animation locations, restore current state
+                setScaleX(scaleX);
+                setScaleY(scaleY);
+                workspace.resetTransitionTransform((CellLayout) getParent().getParent());
+            }
+
+            int[] center = new int[2];
+            float scale = getLocalCenterForIndex(index, center);
+            center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
+            center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+
+            to.offset(center[0] - animateView.getMeasuredWidth() / 2,
+                      center[1] - animateView.getMeasuredHeight() / 2);
+
+            float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
+
+            float finalScale = scale * scaleRelativeToDragLayer;
+            dragLayer.animateView(animateView, from, to, finalAlpha,
+                    1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
+                    new DecelerateInterpolator(2), new AccelerateInterpolator(2),
+                    postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
+            addItem(item);
+            mHiddenItems.add(item);
+            mFolder.hideItem(item);
+            postDelayed(new Runnable() {
+                public void run() {
+                    mHiddenItems.remove(item);
+                    mFolder.showItem(item);
+                    invalidate();
+                }
+            }, DROP_IN_ANIMATION_DURATION);
+        } else {
+            addItem(item);
+        }
+    }
+
+    public void onDrop(DragObject d) {
+        ShortcutInfo item;
+        if (d.dragInfo instanceof AppInfo) {
+            // Came from all apps -- make a copy
+            item = ((AppInfo) d.dragInfo).makeShortcut();
+        } else {
+            item = (ShortcutInfo) d.dragInfo;
+        }
+        mFolder.notifyDrop();
+        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d);
+    }
+
+    private void computePreviewDrawingParams(int drawableSize, int totalSize) {
+        if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) {
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+            mIntrinsicIconSize = drawableSize;
+            mTotalWidth = totalSize;
+
+            final int previewSize = mPreviewBackground.getLayoutParams().height;
+            final int previewPadding = FolderRingAnimator.sPreviewPadding;
+
+            mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
+            // cos(45) = 0.707  + ~= 0.1) = 0.8f
+            int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
+
+            int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+            mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
+
+            mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
+            mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
+
+            mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
+            mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
+        }
+    }
+
+    private void computePreviewDrawingParams(Drawable d) {
+        computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
+    }
+
+    class PreviewItemDrawingParams {
+        PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
+            this.transX = transX;
+            this.transY = transY;
+            this.scale = scale;
+            this.overlayAlpha = overlayAlpha;
+        }
+        float transX;
+        float transY;
+        float scale;
+        int overlayAlpha;
+        Drawable drawable;
+    }
+
+    private float getLocalCenterForIndex(int index, int[] center) {
+        mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
+
+        mParams.transX += mPreviewOffsetX;
+        mParams.transY += mPreviewOffsetY;
+        float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
+        float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
+
+        center[0] = (int) Math.round(offsetX);
+        center[1] = (int) Math.round(offsetY);
+        return mParams.scale;
+    }
+
+    private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
+            PreviewItemDrawingParams params) {
+        index = NUM_ITEMS_IN_PREVIEW - index - 1;
+        float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
+        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
+
+        float offset = (1 - r) * mMaxPerspectiveShift;
+        float scaledSize = scale * mBaselineIconSize;
+        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
+
+        // We want to imagine our coordinates from the bottom left, growing up and to the
+        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
+        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
+        float transX = offset + scaleOffsetCorrection;
+        float totalScale = mBaselineIconScale * scale;
+        final int overlayAlpha = (int) (80 * (1 - r));
+
+        if (params == null) {
+            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
+        } else {
+            params.transX = transX;
+            params.transY = transY;
+            params.scale = totalScale;
+            params.overlayAlpha = overlayAlpha;
+        }
+        return params;
+    }
+
+    private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
+        canvas.save();
+        canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY);
+        canvas.scale(params.scale, params.scale);
+        Drawable d = params.drawable;
+
+        if (d != null) {
+            d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
+            d.setFilterBitmap(true);
+            d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
+                    PorterDuff.Mode.SRC_ATOP);
+            d.draw(canvas);
+            d.clearColorFilter();
+            d.setFilterBitmap(false);
+        }
+        canvas.restore();
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mFolder == null) return;
+        if (mFolder.getItemCount() == 0 && !mAnimating) return;
+
+        ArrayList<View> items = mFolder.getItemsInReadingOrder();
+        Drawable d;
+        TextView v;
+
+        // Update our drawing parameters if necessary
+        if (mAnimating) {
+            computePreviewDrawingParams(mAnimParams.drawable);
+        } else {
+            v = (TextView) items.get(0);
+            d = v.getCompoundDrawables()[1];
+            computePreviewDrawingParams(d);
+        }
+
+        int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
+        if (!mAnimating) {
+            for (int i = nItemsInPreview - 1; i >= 0; i--) {
+                v = (TextView) items.get(i);
+                if (!mHiddenItems.contains(v.getTag())) {
+                    d = v.getCompoundDrawables()[1];
+                    mParams = computePreviewItemDrawingParams(i, mParams);
+                    mParams.drawable = d;
+                    drawPreviewItem(canvas, mParams);
+                }
+            }
+        } else {
+            drawPreviewItem(canvas, mAnimParams);
+        }
+    }
+
+    private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
+            final Runnable onCompleteRunnable) {
+        final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null);
+
+        final float scale0 = 1.0f;
+        final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2;
+        final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop();
+        mAnimParams.drawable = d;
+
+        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
+        va.addUpdateListener(new AnimatorUpdateListener(){
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float progress = (Float) animation.getAnimatedValue();
+                if (reverse) {
+                    progress = 1 - progress;
+                    mPreviewBackground.setAlpha(progress);
+                }
+
+                mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0);
+                mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0);
+                mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0);
+                invalidate();
+            }
+        });
+        va.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mAnimating = true;
+            }
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimating = false;
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+            }
+        });
+        va.setDuration(duration);
+        va.start();
+    }
+
+    public void setTextVisible(boolean visible) {
+        if (visible) {
+            mFolderName.setVisibility(VISIBLE);
+        } else {
+            mFolderName.setVisibility(INVISIBLE);
+        }
+    }
+
+    public boolean getTextVisible() {
+        return mFolderName.getVisibility() == VISIBLE;
+    }
+
+    public void onItemsChanged() {
+        invalidate();
+        requestLayout();
+    }
+
+    public void onAdd(ShortcutInfo item) {
+        invalidate();
+        requestLayout();
+    }
+
+    public void onRemove(ShortcutInfo item) {
+        invalidate();
+        requestLayout();
+    }
+
+    public void onTitleChanged(CharSequence title) {
+        mFolderName.setText(title.toString());
+        setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
+                title));
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // Call the superclass onTouchEvent first, because sometimes it changes the state to
+        // isPressed() on an ACTION_UP
+        boolean result = super.onTouchEvent(event);
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mLongPressHelper.postCheckForLongPress();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                mLongPressHelper.cancelLongPress();
+                break;
+        }
+        return result;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        mLongPressHelper.cancelLongPress();
+    }
+}
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
new file mode 100644
index 0000000..bb5ae82
--- /dev/null
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import java.util.ArrayList;
+
+import android.content.ContentValues;
+
+/**
+ * Represents a folder containing shortcuts or apps.
+ */
+class FolderInfo extends ItemInfo {
+
+    /**
+     * Whether this folder has been opened
+     */
+    boolean opened;
+
+    /**
+     * The apps and shortcuts
+     */
+    ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+
+    ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
+
+    FolderInfo() {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+    }
+
+    /**
+     * Add an app or shortcut
+     *
+     * @param item
+     */
+    public void add(ShortcutInfo item) {
+        contents.add(item);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onAdd(item);
+        }
+        itemsChanged();
+    }
+
+    /**
+     * Remove an app or shortcut. Does not change the DB.
+     *
+     * @param item
+     */
+    public void remove(ShortcutInfo item) {
+        contents.remove(item);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onRemove(item);
+        }
+        itemsChanged();
+    }
+
+    public void setTitle(CharSequence title) {
+        this.title = title;
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onTitleChanged(title);
+        }
+    }
+
+    @Override
+    void onAddToDatabase(ContentValues values) {
+        super.onAddToDatabase(values);
+        values.put(LauncherSettings.Favorites.TITLE, title.toString());
+    }
+
+    void addListener(FolderListener listener) {
+        listeners.add(listener);
+    }
+
+    void removeListener(FolderListener listener) {
+        if (listeners.contains(listener)) {
+            listeners.remove(listener);
+        }
+    }
+
+    void itemsChanged() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onItemsChanged();
+        }
+    }
+
+    @Override
+    void unbind() {
+        super.unbind();
+        listeners.clear();
+    }
+
+    interface FolderListener {
+        public void onAdd(ShortcutInfo item);
+        public void onRemove(ShortcutInfo item);
+        public void onTitleChanged(CharSequence title);
+        public void onItemsChanged();
+    }
+
+    @Override
+    public String toString() {
+        return "FolderInfo(id=" + this.id + " type=" + this.itemType
+                + " container=" + this.container + " screen=" + screenId
+                + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
+                + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+    }
+}
diff --git a/src/com/android/launcher3/HideFromAccessibilityHelper.java b/src/com/android/launcher3/HideFromAccessibilityHelper.java
new file mode 100644
index 0000000..75cbb1b
--- /dev/null
+++ b/src/com/android/launcher3/HideFromAccessibilityHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.launcher3;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+
+import java.util.HashMap;
+
+public class HideFromAccessibilityHelper implements OnHierarchyChangeListener {
+    private HashMap<View, Integer> mPreviousValues;
+    boolean mHide;
+    boolean mOnlyAllApps;
+
+    public HideFromAccessibilityHelper() {
+        mPreviousValues = new HashMap<View, Integer>();
+        mHide = false;
+    }
+
+    public void setImportantForAccessibilityToNo(View v, boolean onlyAllApps) {
+        mOnlyAllApps = onlyAllApps;
+        setImportantForAccessibilityToNoHelper(v);
+        mHide = true;
+    }
+
+    private void setImportantForAccessibilityToNoHelper(View v) {
+        mPreviousValues.put(v, v.getImportantForAccessibility());
+        v.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
+        // Call method on children recursively
+        if (v instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) v;
+            vg.setOnHierarchyChangeListener(this);
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                View child = vg.getChildAt(i);
+
+                if (includeView(child)) {
+                    setImportantForAccessibilityToNoHelper(child);
+                }
+            }
+        }
+    }
+
+    public void restoreImportantForAccessibility(View v) {
+        if (mHide) {
+            restoreImportantForAccessibilityHelper(v);
+        }
+        mHide = false;
+    }
+
+    private void restoreImportantForAccessibilityHelper(View v) {
+        Integer important = mPreviousValues.get(v);
+        v.setImportantForAccessibility(important);
+        mPreviousValues.remove(v);
+
+        // Call method on children recursively
+        if (v instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) v;
+
+            // We assume if a class implements OnHierarchyChangeListener, it listens
+            // to changes to any of its children (happens to be the case in Launcher)
+            if (vg instanceof OnHierarchyChangeListener) {
+                vg.setOnHierarchyChangeListener((OnHierarchyChangeListener) vg);
+            } else {
+                vg.setOnHierarchyChangeListener(null);
+            }
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                View child = vg.getChildAt(i);
+                if (includeView(child)) {
+                    restoreImportantForAccessibilityHelper(child);
+                }
+            }
+        }
+    }
+
+    public void onChildViewAdded(View parent, View child) {
+        if (mHide && includeView(child)) {
+            setImportantForAccessibilityToNoHelper(child);
+        }
+    }
+
+    public void onChildViewRemoved(View parent, View child) {
+        if (mHide && includeView(child)) {
+            restoreImportantForAccessibilityHelper(child);
+        }
+    }
+
+    private boolean includeView(View v) {
+        return !hasAncestorOfType(v, Cling.class) &&
+                (!mOnlyAllApps || hasAncestorOfType(v, AppsCustomizeTabHost.class));
+    }
+
+    private boolean hasAncestorOfType(View v, Class c) {
+        return v != null &&
+                (v.getClass().equals(c) ||
+                 (v.getParent() instanceof ViewGroup &&
+                  hasAncestorOfType((ViewGroup) v.getParent(), c)));
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/HolographicImageView.java b/src/com/android/launcher3/HolographicImageView.java
new file mode 100644
index 0000000..18ac092
--- /dev/null
+++ b/src/com/android/launcher3/HolographicImageView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+
+public class HolographicImageView extends ImageView {
+
+    private final HolographicViewHelper mHolographicHelper;
+    private boolean mHotwordOn;
+    private boolean mIsPressed;
+    private boolean mIsFocused;
+
+    public HolographicImageView(Context context) {
+        this(context, null);
+    }
+
+    public HolographicImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HolographicImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mHolographicHelper = new HolographicViewHelper(context);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout,
+                defStyle, 0);
+        mHotwordOn = a.getBoolean(R.styleable.HolographicLinearLayout_stateHotwordOn, false);
+        a.recycle();
+
+        setOnTouchListener(new OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (isPressed() != mIsPressed) {
+                    mIsPressed = isPressed();
+                    refreshDrawableState();
+                }
+                return false;
+            }
+        });
+
+        setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (isFocused() != mIsFocused) {
+                    mIsFocused = isFocused();
+                    refreshDrawableState();
+                }
+            }
+        });
+    }
+
+    void invalidatePressedFocusedStates() {
+        mHolographicHelper.invalidatePressedFocusedStates(this);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        mHolographicHelper.generatePressedFocusedStates(this);
+        Drawable d = getDrawable();
+        if (d instanceof StateListDrawable) {
+            StateListDrawable sld = (StateListDrawable) d;
+            sld.setState(getDrawableState());
+            sld.invalidateSelf();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // One time call to generate the pressed/focused state -- must be called after
+        // measure/layout
+        mHolographicHelper.generatePressedFocusedStates(this);
+    }
+
+    private boolean isHotwordOn() {
+        return mHotwordOn;
+    }
+
+    public void setHotwordState(boolean on) {
+        if (on == mHotwordOn) {
+            return;
+        }
+        mHotwordOn = on;
+        refreshDrawableState();
+    }
+
+    @Override
+    public int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isHotwordOn()) {
+            mergeDrawableStates(drawableState, new int[] {R.attr.stateHotwordOn});
+        }
+        return drawableState;
+    }
+}
diff --git a/src/com/android/launcher3/HolographicLinearLayout.java b/src/com/android/launcher3/HolographicLinearLayout.java
new file mode 100644
index 0000000..5344a7e
--- /dev/null
+++ b/src/com/android/launcher3/HolographicLinearLayout.java
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class HolographicLinearLayout extends LinearLayout {
+    private final HolographicViewHelper mHolographicHelper;
+    private ImageView mImageView;
+    private int mImageViewId;
+
+    private boolean mHotwordOn;
+    private boolean mIsPressed;
+    private boolean mIsFocused;
+
+    public HolographicLinearLayout(Context context) {
+        this(context, null);
+    }
+
+    public HolographicLinearLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HolographicLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout,
+                defStyle, 0);
+        mImageViewId = a.getResourceId(R.styleable.HolographicLinearLayout_sourceImageViewId, -1);
+        mHotwordOn = a.getBoolean(R.styleable.HolographicLinearLayout_stateHotwordOn, false);
+        a.recycle();
+
+
+        setWillNotDraw(false);
+        mHolographicHelper = new HolographicViewHelper(context);
+
+        setOnTouchListener(new OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (isPressed() != mIsPressed) {
+                    mIsPressed = isPressed();
+                    refreshDrawableState();
+                }
+                return false;
+            }
+        });
+
+        setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (isFocused() != mIsFocused) {
+                    mIsFocused = isFocused();
+                    refreshDrawableState();
+                }
+            }
+        });
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if (mImageView != null) {
+            mHolographicHelper.generatePressedFocusedStates(mImageView);
+            Drawable d = mImageView.getDrawable();
+            if (d instanceof StateListDrawable) {
+                StateListDrawable sld = (StateListDrawable) d;
+                sld.setState(getDrawableState());
+                sld.invalidateSelf();
+            }
+        }
+    }
+
+    void invalidatePressedFocusedStates() {
+        mHolographicHelper.invalidatePressedFocusedStates(mImageView);
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // One time call to generate the pressed/focused state -- must be called after
+        // measure/layout
+        if (mImageView == null) {
+            mImageView = (ImageView) findViewById(mImageViewId);
+        }
+        mHolographicHelper.generatePressedFocusedStates(mImageView);
+    }
+
+    private boolean isHotwordOn() {
+        return mHotwordOn;
+    }
+
+    public void setHotwordState(boolean on) {
+        if (on == mHotwordOn) {
+            return;
+        }
+        mHotwordOn = on;
+        refreshDrawableState();
+    }
+
+    @Override
+    public int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isHotwordOn()) {
+            mergeDrawableStates(drawableState, new int[] {R.attr.stateHotwordOn});
+        }
+        return drawableState;
+    }
+}
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java
new file mode 100644
index 0000000..d7b960a
--- /dev/null
+++ b/src/com/android/launcher3/HolographicOutlineHelper.java
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+
+public class HolographicOutlineHelper {
+    private final Paint mHolographicPaint = new Paint();
+    private final Paint mBlurPaint = new Paint();
+    private final Paint mErasePaint = new Paint();
+
+    public int mMaxOuterBlurRadius;
+    public int mMinOuterBlurRadius;
+
+    private BlurMaskFilter mExtraThickOuterBlurMaskFilter;
+    private BlurMaskFilter mThickOuterBlurMaskFilter;
+    private BlurMaskFilter mMediumOuterBlurMaskFilter;
+    private BlurMaskFilter mThinOuterBlurMaskFilter;
+    private BlurMaskFilter mThickInnerBlurMaskFilter;
+    private BlurMaskFilter mExtraThickInnerBlurMaskFilter;
+    private BlurMaskFilter mMediumInnerBlurMaskFilter;
+
+    private static final int THICK = 0;
+    private static final int MEDIUM = 1;
+    private static final int EXTRA_THICK = 2;
+
+    static HolographicOutlineHelper INSTANCE;
+
+    private HolographicOutlineHelper(Context context) {
+        final float scale = LauncherAppState.getInstance().getScreenDensity();
+
+        mMinOuterBlurRadius = (int) (scale * 1.0f);
+        mMaxOuterBlurRadius = (int) (scale * 12.0f);
+
+        mExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER);
+        mThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER);
+        mMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER);
+        mThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER);
+        mExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL);
+        mThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
+        mMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL);
+
+        mHolographicPaint.setFilterBitmap(true);
+        mHolographicPaint.setAntiAlias(true);
+        mBlurPaint.setFilterBitmap(true);
+        mBlurPaint.setAntiAlias(true);
+        mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+        mErasePaint.setFilterBitmap(true);
+        mErasePaint.setAntiAlias(true);
+    }
+
+    public static HolographicOutlineHelper obtain(Context context) {
+        if (INSTANCE == null) {
+            INSTANCE = new HolographicOutlineHelper(context);
+        }
+        return INSTANCE;
+    }
+
+    /**
+     * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
+     * pages.
+     */
+    public static float highlightAlphaInterpolator(float r) {
+        float maxAlpha = 0.6f;
+        return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f);
+    }
+
+    /**
+     * Returns the interpolated view alpha for the effect we want when scrolling pages.
+     */
+    public static float viewAlphaInterpolator(float r) {
+        final float pivot = 0.95f;
+        if (r < pivot) {
+            return (float) Math.pow(r / pivot, 1.5f);
+        } else {
+            return 1.0f;
+        }
+    }
+
+    /**
+     * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
+     * bitmap.
+     */
+    void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor, int thickness) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true,
+                thickness);
+    }
+    void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor, boolean clipAlpha, int thickness) {
+
+        // We start by removing most of the alpha channel so as to ignore shadows, and
+        // other types of partial transparency when defining the shape of the object
+        if (clipAlpha) {
+            int[] srcBuffer = new int[srcDst.getWidth() * srcDst.getHeight()];
+            srcDst.getPixels(srcBuffer,
+                    0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
+            for (int i = 0; i < srcBuffer.length; i++) {
+                final int alpha = srcBuffer[i] >>> 24;
+                if (alpha < 188) {
+                    srcBuffer[i] = 0;
+                }
+            }
+            srcDst.setPixels(srcBuffer,
+                    0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
+        }
+        Bitmap glowShape = srcDst.extractAlpha();
+
+        // calculate the outer blur first
+        BlurMaskFilter outerBlurMaskFilter;
+        switch (thickness) {
+            case EXTRA_THICK:
+                outerBlurMaskFilter = mExtraThickOuterBlurMaskFilter;
+                break;
+            case THICK:
+                outerBlurMaskFilter = mThickOuterBlurMaskFilter;
+                break;
+            case MEDIUM:
+                outerBlurMaskFilter = mMediumOuterBlurMaskFilter;
+                break;
+            default:
+                throw new RuntimeException("Invalid blur thickness");
+        }
+        mBlurPaint.setMaskFilter(outerBlurMaskFilter);
+        int[] outerBlurOffset = new int[2];
+        Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset);
+        if (thickness == EXTRA_THICK) {
+            mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
+        } else {
+            mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
+        }
+
+        int[] brightOutlineOffset = new int[2];
+        Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset);
+
+        // calculate the inner blur
+        srcDstCanvas.setBitmap(glowShape);
+        srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
+        BlurMaskFilter innerBlurMaskFilter;
+        switch (thickness) {
+            case EXTRA_THICK:
+                innerBlurMaskFilter = mExtraThickInnerBlurMaskFilter;
+                break;
+            case THICK:
+                innerBlurMaskFilter = mThickInnerBlurMaskFilter;
+                break;
+            case MEDIUM:
+                innerBlurMaskFilter = mMediumInnerBlurMaskFilter;
+                break;
+            default:
+                throw new RuntimeException("Invalid blur thickness");
+        }
+        mBlurPaint.setMaskFilter(innerBlurMaskFilter);
+        int[] thickInnerBlurOffset = new int[2];
+        Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset);
+
+        // mask out the inner blur
+        srcDstCanvas.setBitmap(thickInnerBlur);
+        srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0],
+                -thickInnerBlurOffset[1], mErasePaint);
+        srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
+                mErasePaint);
+        srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
+                mErasePaint);
+
+        // draw the inner and outer blur
+        srcDstCanvas.setBitmap(srcDst);
+        srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        mHolographicPaint.setColor(color);
+        srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
+                mHolographicPaint);
+        srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
+                mHolographicPaint);
+
+        // draw the bright outline
+        mHolographicPaint.setColor(outlineColor);
+        srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
+                mHolographicPaint);
+
+        // cleanup
+        srcDstCanvas.setBitmap(null);
+        brightOutline.recycle();
+        thickOuterBlur.recycle();
+        thickInnerBlur.recycle();
+        glowShape.recycle();
+    }
+
+    void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK);
+    }
+
+    void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK);
+    }
+
+    void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor, boolean clipAlpha) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, clipAlpha,
+                MEDIUM);
+    }
+
+    void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+            int outlineColor) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM);
+    }
+
+}
diff --git a/src/com/android/launcher3/HolographicViewHelper.java b/src/com/android/launcher3/HolographicViewHelper.java
new file mode 100644
index 0000000..7ef0355
--- /dev/null
+++ b/src/com/android/launcher3/HolographicViewHelper.java
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.widget.ImageView;
+
+public class HolographicViewHelper {
+
+    private final Canvas mTempCanvas = new Canvas();
+
+    private boolean mStatesUpdated;
+    private int mHighlightColor, mHotwordColor;
+
+    public HolographicViewHelper(Context context) {
+        Resources res = context.getResources();
+        mHighlightColor = res.getColor(android.R.color.holo_blue_light);
+        mHotwordColor = res.getColor(android.R.color.holo_green_light);
+    }
+
+    /**
+     * Generate the pressed/focused states if necessary.
+     */
+    void generatePressedFocusedStates(ImageView v) {
+        if (!mStatesUpdated && v != null) {
+            mStatesUpdated = true;
+            Bitmap original = createOriginalImage(v, mTempCanvas);
+            Bitmap outline = createImageWithOverlay(v, mTempCanvas, mHighlightColor);
+            Bitmap hotword = createImageWithOverlay(v, mTempCanvas, mHotwordColor);
+            FastBitmapDrawable originalD = new FastBitmapDrawable(original);
+            FastBitmapDrawable outlineD = new FastBitmapDrawable(outline);
+            FastBitmapDrawable hotwordD = new FastBitmapDrawable(hotword);
+
+            StateListDrawable states = new StateListDrawable();
+
+            states.addState(new int[] {android.R.attr.state_pressed}, outlineD);
+            states.addState(new int[] {android.R.attr.state_focused}, outlineD);
+            states.addState(new int[] {R.attr.stateHotwordOn}, hotwordD);
+            states.addState(new int[] {}, originalD);
+            v.setImageDrawable(states);
+        }
+    }
+
+    /**
+     * Invalidates the pressed/focused states.
+     */
+    void invalidatePressedFocusedStates(ImageView v) {
+        mStatesUpdated = false;
+        if (v != null) {
+            v.invalidate();
+        }
+    }
+
+    /**
+     * Creates a copy of the original image.
+     */
+    private Bitmap createOriginalImage(ImageView v, Canvas canvas) {
+        final Drawable d = v.getDrawable();
+        final Bitmap b = Bitmap.createBitmap(
+                d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        canvas.save();
+        d.draw(canvas);
+        canvas.restore();
+        canvas.setBitmap(null);
+
+        return b;
+    }
+
+    /**
+     * Creates a new press state image which is the old image with a blue overlay.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createImageWithOverlay(ImageView v, Canvas canvas, int color) {
+        final Drawable d = v.getDrawable();
+        final Bitmap b = Bitmap.createBitmap(
+                d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        canvas.save();
+        d.draw(canvas);
+        canvas.restore();
+        canvas.drawColor(color, PorterDuff.Mode.SRC_IN);
+        canvas.setBitmap(null);
+
+        return b;
+    }
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
new file mode 100644
index 0000000..2aab64d
--- /dev/null
+++ b/src/com/android/launcher3/Hotseat.java
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class Hotseat extends FrameLayout {
+    private static final String TAG = "Hotseat";
+
+    private CellLayout mContent;
+
+    private Launcher mLauncher;
+
+    private int mAllAppsButtonRank;
+
+    private boolean mTransposeLayoutWithOrientation;
+    private boolean mIsLandscape;
+
+    public Hotseat(Context context) {
+        this(context, null);
+    }
+
+    public Hotseat(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public Hotseat(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        Resources r = context.getResources();
+        mTransposeLayoutWithOrientation = 
+                r.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+        mIsLandscape = context.getResources().getConfiguration().orientation ==
+            Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    public void setup(Launcher launcher) {
+        mLauncher = launcher;
+        setOnKeyListener(new HotseatIconKeyEventListener());
+    }
+
+    CellLayout getLayout() {
+        return mContent;
+    }
+
+    /**
+     * Registers the specified listener on the cell layout of the hotseat.
+     */
+    @Override
+    public void setOnLongClickListener(OnLongClickListener l) {
+        mContent.setOnLongClickListener(l);
+    }
+  
+    private boolean hasVerticalHotseat() {
+        return (mIsLandscape && mTransposeLayoutWithOrientation);
+    }
+
+    /* Get the orientation invariant order of the item in the hotseat for persistence. */
+    int getOrderInHotseat(int x, int y) {
+        return hasVerticalHotseat() ? (mContent.getCountY() - y - 1) : x;
+    }
+    /* Get the orientation specific coordinates given an invariant order in the hotseat. */
+    int getCellXFromOrder(int rank) {
+        return hasVerticalHotseat() ? 0 : rank;
+    }
+    int getCellYFromOrder(int rank) {
+        return hasVerticalHotseat() ? (mContent.getCountY() - (rank + 1)) : 0;
+    }
+    public boolean isAllAppsButtonRank(int rank) {
+        if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            return false;
+        } else {
+            return rank == mAllAppsButtonRank;
+        }
+    }
+
+    /** This returns the coordinates of an app in a given cell, relative to the DragLayer */
+    Rect getCellCoordinates(int cellX, int cellY) {
+        Rect coords = new Rect();
+        mContent.cellToRect(cellX, cellY, 1, 1, coords);
+        int[] hotseatInParent = new int[2];
+        Utilities.getDescendantCoordRelativeToParent(this, mLauncher.getDragLayer(),
+                hotseatInParent, false);
+        coords.offset(hotseatInParent[0], hotseatInParent[1]);
+
+        // Center the icon
+        int cWidth = mContent.getShortcutsAndWidgets().getCellContentWidth();
+        int cHeight = mContent.getShortcutsAndWidgets().getCellContentHeight();
+        int cellPaddingX = (int) Math.max(0, ((coords.width() - cWidth) / 2f));
+        int cellPaddingY = (int) Math.max(0, ((coords.height() - cHeight) / 2f));
+        coords.offset(cellPaddingX, cellPaddingY);
+
+        return coords;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        mAllAppsButtonRank = grid.hotseatAllAppsRank;
+        mContent = (CellLayout) findViewById(R.id.layout);
+        if (grid.isLandscape && !grid.isLargeTablet()) {
+            mContent.setGridSize(1, (int) grid.numHotseatIcons);
+        } else {
+            mContent.setGridSize((int) grid.numHotseatIcons, 1);
+        }
+        mContent.setIsHotseat(true);
+
+        resetLayout();
+    }
+
+    void resetLayout() {
+        mContent.removeAllViewsInLayout();
+
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            // Add the Apps button
+            Context context = getContext();
+
+            LayoutInflater inflater = LayoutInflater.from(context);
+            TextView allAppsButton = (TextView)
+                    inflater.inflate(R.layout.all_apps_button, mContent, false);
+            Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+            d.setBounds(0, 0, Utilities.sIconTextureWidth, Utilities.sIconTextureHeight);
+            allAppsButton.setCompoundDrawables(null, d, null, null);
+
+            allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
+            if (mLauncher != null) {
+                allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+            }
+            allAppsButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(android.view.View v) {
+                    if (mLauncher != null) {
+                        mLauncher.onClickAllAppsButton(v);
+                    }
+                }
+            });
+
+            // Note: We do this to ensure that the hotseat is always laid out in the orientation of
+            // the hotseat in order regardless of which orientation they were added
+            int x = getCellXFromOrder(mAllAppsButtonRank);
+            int y = getCellYFromOrder(mAllAppsButtonRank);
+            CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1);
+            lp.canReorder = false;
+            mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // We don't want any clicks to go through to the hotseat unless the workspace is in
+        // the normal state.
+        if (mLauncher.getWorkspace().isSmall()) {
+            return true;
+        }
+        return false;
+    }
+
+    void addAllAppsFolder(IconCache iconCache,
+            ArrayList<AppInfo> allApps, ArrayList<ComponentName> onWorkspace,
+            Launcher launcher, Workspace workspace) {
+        if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            FolderInfo fi = new FolderInfo();
+
+            fi.cellX = getCellXFromOrder(mAllAppsButtonRank);
+            fi.cellY = getCellYFromOrder(mAllAppsButtonRank);
+            fi.spanX = 1;
+            fi.spanY = 1;
+            fi.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+            fi.screenId = mAllAppsButtonRank;
+            fi.itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+            fi.title = "More Apps";
+            LauncherModel.addItemToDatabase(launcher, fi, fi.container, fi.screenId, fi.cellX,
+                    fi.cellY, false);
+            FolderIcon folder = FolderIcon.fromXml(R.layout.folder_icon, launcher,
+                    getLayout(), fi, iconCache);
+            workspace.addInScreen(folder, fi.container, fi.screenId, fi.cellX, fi.cellY,
+                    fi.spanX, fi.spanY);
+
+            for (AppInfo info: allApps) {
+                ComponentName cn = info.intent.getComponent();
+                if (!onWorkspace.contains(cn)) {
+                    Log.d(TAG, "Adding to 'more apps': " + info.intent);
+                    ShortcutInfo si = info.makeShortcut();
+                    fi.add(si);
+                }
+            }
+        }
+    }
+
+    void addAppsToAllAppsFolder(ArrayList<AppInfo> apps) {
+        if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            View v = mContent.getChildAt(getCellXFromOrder(mAllAppsButtonRank), getCellYFromOrder(mAllAppsButtonRank));
+            FolderIcon fi = null;
+
+            if (v instanceof FolderIcon) {
+                fi = (FolderIcon) v;
+            } else {
+                return;
+            }
+
+            FolderInfo info = fi.getFolderInfo();
+            for (AppInfo a: apps) {
+                ShortcutInfo si = a.makeShortcut();
+                info.add(si);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
new file mode 100644
index 0000000..1797826
--- /dev/null
+++ b/src/com/android/launcher3/IconCache.java
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+
+import java.util.HashMap;
+
+/**
+ * Cache of application icons.  Icons can be made from any thread.
+ */
+public class IconCache {
+    @SuppressWarnings("unused")
+    private static final String TAG = "Launcher.IconCache";
+
+    private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
+
+    private static class CacheEntry {
+        public Bitmap icon;
+        public String title;
+    }
+
+    private final Bitmap mDefaultIcon;
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final HashMap<ComponentName, CacheEntry> mCache =
+            new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+    private int mIconDpi;
+
+    public IconCache(Context context) {
+        ActivityManager activityManager =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mIconDpi = activityManager.getLauncherLargeIconDensity();
+
+        // need to set mIconDpi before getting default icon
+        mDefaultIcon = makeDefaultIcon();
+    }
+
+    public Drawable getFullResDefaultActivityIcon() {
+        return getFullResIcon(Resources.getSystem(),
+                android.R.mipmap.sym_def_app_icon);
+    }
+
+    public Drawable getFullResIcon(Resources resources, int iconId) {
+        Drawable d;
+        try {
+            d = resources.getDrawableForDensity(iconId, mIconDpi);
+        } catch (Resources.NotFoundException e) {
+            d = null;
+        }
+
+        return (d != null) ? d : getFullResDefaultActivityIcon();
+    }
+
+    public Drawable getFullResIcon(String packageName, int iconId) {
+        Resources resources;
+        try {
+            resources = mPackageManager.getResourcesForApplication(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            resources = null;
+        }
+        if (resources != null) {
+            if (iconId != 0) {
+                return getFullResIcon(resources, iconId);
+            }
+        }
+        return getFullResDefaultActivityIcon();
+    }
+
+    public Drawable getFullResIcon(ResolveInfo info) {
+        return getFullResIcon(info.activityInfo);
+    }
+
+    public Drawable getFullResIcon(ActivityInfo info) {
+
+        Resources resources;
+        try {
+            resources = mPackageManager.getResourcesForApplication(
+                    info.applicationInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            resources = null;
+        }
+        if (resources != null) {
+            int iconId = info.getIconResource();
+            if (iconId != 0) {
+                return getFullResIcon(resources, iconId);
+            }
+        }
+        return getFullResDefaultActivityIcon();
+    }
+
+    private Bitmap makeDefaultIcon() {
+        Drawable d = getFullResDefaultActivityIcon();
+        Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
+                Math.max(d.getIntrinsicHeight(), 1),
+                Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(b);
+        d.setBounds(0, 0, b.getWidth(), b.getHeight());
+        d.draw(c);
+        c.setBitmap(null);
+        return b;
+    }
+
+    /**
+     * Remove any records for the supplied ComponentName.
+     */
+    public void remove(ComponentName componentName) {
+        synchronized (mCache) {
+            mCache.remove(componentName);
+        }
+    }
+
+    /**
+     * Empty out the cache.
+     */
+    public void flush() {
+        synchronized (mCache) {
+            mCache.clear();
+        }
+    }
+
+    /**
+     * Fill in "application" with the icon and label for "info."
+     */
+    public void getTitleAndIcon(AppInfo application, ResolveInfo info,
+            HashMap<Object, CharSequence> labelCache) {
+        synchronized (mCache) {
+            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
+
+            application.title = entry.title;
+            application.iconBitmap = entry.icon;
+        }
+    }
+
+    public Bitmap getIcon(Intent intent) {
+        synchronized (mCache) {
+            final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
+            ComponentName component = intent.getComponent();
+
+            if (resolveInfo == null || component == null) {
+                return mDefaultIcon;
+            }
+
+            CacheEntry entry = cacheLocked(component, resolveInfo, null);
+            return entry.icon;
+        }
+    }
+
+    public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
+            HashMap<Object, CharSequence> labelCache) {
+        synchronized (mCache) {
+            if (resolveInfo == null || component == null) {
+                return null;
+            }
+
+            CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
+            return entry.icon;
+        }
+    }
+
+    public boolean isDefaultIcon(Bitmap icon) {
+        return mDefaultIcon == icon;
+    }
+
+    private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
+            HashMap<Object, CharSequence> labelCache) {
+        CacheEntry entry = mCache.get(componentName);
+        if (entry == null) {
+            entry = new CacheEntry();
+
+            mCache.put(componentName, entry);
+
+            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
+            if (labelCache != null && labelCache.containsKey(key)) {
+                entry.title = labelCache.get(key).toString();
+            } else {
+                entry.title = info.loadLabel(mPackageManager).toString();
+                if (labelCache != null) {
+                    labelCache.put(key, entry.title);
+                }
+            }
+            if (entry.title == null) {
+                entry.title = info.activityInfo.name;
+            }
+
+            entry.icon = Utilities.createIconBitmap(
+                    getFullResIcon(info), mContext);
+        }
+        return entry;
+    }
+
+    public HashMap<ComponentName,Bitmap> getAllIcons() {
+        synchronized (mCache) {
+            HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
+            for (ComponentName cn : mCache.keySet()) {
+                final CacheEntry e = mCache.get(cn);
+                set.put(cn, e.icon);
+            }
+            return set;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
new file mode 100644
index 0000000..2ad43b6
--- /dev/null
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class InfoDropTarget extends ButtonDropTarget {
+
+    private ColorStateList mOriginalTextColor;
+    private TransitionDrawable mDrawable;
+
+    public InfoDropTarget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public InfoDropTarget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mOriginalTextColor = getTextColors();
+
+        // Get the hover color
+        Resources r = getResources();
+        mHoverColor = r.getColor(R.color.info_target_hover_tint);
+        mDrawable = (TransitionDrawable) getCurrentDrawable();
+        if (null != mDrawable) {
+            mDrawable.setCrossFadeEnabled(true);
+        }
+
+        // Remove the text in the Phone UI in landscape
+        int orientation = getResources().getConfiguration().orientation;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            if (!LauncherAppState.getInstance().isScreenLarge()) {
+                setText("");
+            }
+        }
+    }
+
+    private boolean isFromAllApps(DragSource source) {
+        return (source instanceof AppsCustomizePagedView);
+    }
+
+    @Override
+    public boolean acceptDrop(DragObject d) {
+        // acceptDrop is called just before onDrop. We do the work here, rather than
+        // in onDrop, because it allows us to reject the drop (by returning false)
+        // so that the object being dragged isn't removed from the drag source.
+        ComponentName componentName = null;
+        if (d.dragInfo instanceof AppInfo) {
+            componentName = ((AppInfo) d.dragInfo).componentName;
+        } else if (d.dragInfo instanceof ShortcutInfo) {
+            componentName = ((ShortcutInfo) d.dragInfo).intent.getComponent();
+        } else if (d.dragInfo instanceof PendingAddItemInfo) {
+            componentName = ((PendingAddItemInfo) d.dragInfo).componentName;
+        }
+        if (componentName != null) {
+            mLauncher.startApplicationDetailsActivity(componentName);
+        }
+
+        // There is no post-drop animation, so clean up the DragView now
+        d.deferDragViewCleanupPostAnimation = false;
+        return false;
+    }
+
+    @Override
+    public void onDragStart(DragSource source, Object info, int dragAction) {
+        boolean isVisible = true;
+
+        // Hide this button unless we are dragging something from AllApps
+        if (!isFromAllApps(source)) {
+            isVisible = false;
+        }
+
+        mActive = isVisible;
+        mDrawable.resetTransition();
+        setTextColor(mOriginalTextColor);
+        ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void onDragEnd() {
+        super.onDragEnd();
+        mActive = false;
+    }
+
+    public void onDragEnter(DragObject d) {
+        super.onDragEnter(d);
+
+        mDrawable.startTransition(mTransitionDuration);
+        setTextColor(mHoverColor);
+    }
+
+    public void onDragExit(DragObject d) {
+        super.onDragExit(d);
+
+        if (!d.dragComplete) {
+            mDrawable.resetTransition();
+            setTextColor(mOriginalTextColor);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Insettable.java b/src/com/android/launcher3/Insettable.java
new file mode 100644
index 0000000..1d2356c
--- /dev/null
+++ b/src/com/android/launcher3/Insettable.java
@@ -0,0 +1,24 @@
+/*
+ * 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.launcher3;
+
+import android.graphics.Rect;
+
+public interface Insettable {
+
+    void setInsets(Rect insets);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
new file mode 100644
index 0000000..821c15f
--- /dev/null
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -0,0 +1,309 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Base64;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.json.*;
+
+public class InstallShortcutReceiver extends BroadcastReceiver {
+    public static final String ACTION_INSTALL_SHORTCUT =
+            "com.android.launcher.action.INSTALL_SHORTCUT";
+
+    public static final String DATA_INTENT_KEY = "intent.data";
+    public static final String LAUNCH_INTENT_KEY = "intent.launch";
+    public static final String NAME_KEY = "name";
+    public static final String ICON_KEY = "icon";
+    public static final String ICON_RESOURCE_NAME_KEY = "iconResource";
+    public static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
+    // The set of shortcuts that are pending install
+    public static final String APPS_PENDING_INSTALL = "apps_to_install";
+
+    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
+
+    private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
+    private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
+
+    // A mime-type representing shortcut data
+    public static final String SHORTCUT_MIMETYPE =
+            "com.android.launcher3/shortcut";
+
+    private static Object sLock = new Object();
+
+    private static void addToStringSet(SharedPreferences sharedPrefs,
+            SharedPreferences.Editor editor, String key, String value) {
+        Set<String> strings = sharedPrefs.getStringSet(key, null);
+        if (strings == null) {
+            strings = new HashSet<String>(0);
+        } else {
+            strings = new HashSet<String>(strings);
+        }
+        strings.add(value);
+        editor.putStringSet(key, strings);
+    }
+
+    private static void addToInstallQueue(
+            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
+        synchronized(sLock) {
+            try {
+                JSONStringer json = new JSONStringer()
+                    .object()
+                    .key(DATA_INTENT_KEY).value(info.data.toUri(0))
+                    .key(LAUNCH_INTENT_KEY).value(info.launchIntent.toUri(0))
+                    .key(NAME_KEY).value(info.name);
+                if (info.icon != null) {
+                    byte[] iconByteArray = ItemInfo.flattenBitmap(info.icon);
+                    json = json.key(ICON_KEY).value(
+                        Base64.encodeToString(
+                            iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+                }
+                if (info.iconResource != null) {
+                    json = json.key(ICON_RESOURCE_NAME_KEY).value(info.iconResource.resourceName);
+                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
+                        .value(info.iconResource.packageName);
+                }
+                json = json.endObject();
+                SharedPreferences.Editor editor = sharedPrefs.edit();
+                addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
+                editor.commit();
+            } catch (org.json.JSONException e) {
+                Log.d("InstallShortcutReceiver", "Exception when adding shortcut: " + e);
+            }
+        }
+    }
+
+    public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
+                                              ArrayList<String> packageNames) {
+        synchronized(sLock) {
+            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            if (strings != null) {
+                Set<String> newStrings = new HashSet<String>(strings);
+                for (String json : newStrings) {
+                    try {
+                        JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+                        Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+                        String pn = launchIntent.getPackage();
+                        if (pn == null) {
+                            pn = launchIntent.getComponent().getPackageName();
+                        }
+                        if (packageNames.contains(pn)) {
+                            newStrings.remove(json);
+                        }
+                    } catch (org.json.JSONException e) {
+                        Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+                    } catch (java.net.URISyntaxException e) {
+                        Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+                    }
+                }
+                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
+                        new HashSet<String>(newStrings)).commit();
+            }
+        }
+    }
+
+    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
+            SharedPreferences sharedPrefs) {
+        synchronized(sLock) {
+            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            if (strings == null) {
+                return new ArrayList<PendingInstallShortcutInfo>();
+            }
+            ArrayList<PendingInstallShortcutInfo> infos =
+                new ArrayList<PendingInstallShortcutInfo>();
+            for (String json : strings) {
+                try {
+                    JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+                    Intent data = Intent.parseUri(object.getString(DATA_INTENT_KEY), 0);
+                    Intent launchIntent =
+                            Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+                    String name = object.getString(NAME_KEY);
+                    String iconBase64 = object.optString(ICON_KEY);
+                    String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
+                    String iconResourcePackageName =
+                        object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+                    if (iconBase64 != null && !iconBase64.isEmpty()) {
+                        byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
+                        Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
+                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
+                    } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
+                        Intent.ShortcutIconResource iconResource =
+                            new Intent.ShortcutIconResource();
+                        iconResource.resourceName = iconResourceName;
+                        iconResource.packageName = iconResourcePackageName;
+                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+                    }
+                    data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
+                    PendingInstallShortcutInfo info =
+                        new PendingInstallShortcutInfo(data, name, launchIntent);
+                    infos.add(info);
+                } catch (org.json.JSONException e) {
+                    Log.d("InstallShortcutReceiver",
+                            "Exception reading shortcut to add: " + e);
+                } catch (java.net.URISyntaxException e) {
+                    Log.d("InstallShortcutReceiver",
+                            "Exception reading shortcut to add: " + e);
+                }
+            }
+            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
+            return infos;
+        }
+    }
+
+    // Determines whether to defer installing shortcuts immediately until
+    // processAllPendingInstalls() is called.
+    private static boolean mUseInstallQueue = false;
+
+    private static class PendingInstallShortcutInfo {
+        Intent data;
+        Intent launchIntent;
+        String name;
+        Bitmap icon;
+        Intent.ShortcutIconResource iconResource;
+
+        public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
+                Intent shortcutIntent) {
+            data = rawData;
+            name = shortcutName;
+            launchIntent = shortcutIntent;
+        }
+    }
+
+    public void onReceive(Context context, Intent data) {
+        if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
+            return;
+        }
+
+        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        if (intent == null) {
+            return;
+        }
+        // This name is only used for comparisons and notifications, so fall back to activity name
+        // if not supplied
+        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        if (name == null) {
+            try {
+                PackageManager pm = context.getPackageManager();
+                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
+                name = info.loadLabel(pm).toString();
+            } catch (PackageManager.NameNotFoundException nnfe) {
+                return;
+            }
+        }
+        Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+        Intent.ShortcutIconResource iconResource =
+            data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+
+        // Queue the item up for adding if launcher has not loaded properly yet
+        LauncherAppState.setApplicationContext(context.getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+        boolean launcherNotLoaded = (app.getDynamicGrid() == null);
+
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
+        info.icon = icon;
+        info.iconResource = iconResource;
+
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        addToInstallQueue(sp, info);
+        if (!mUseInstallQueue && !launcherNotLoaded) {
+            flushInstallQueue(context);
+        }
+    }
+
+    static void enableInstallQueue() {
+        mUseInstallQueue = true;
+    }
+    static void disableAndFlushInstallQueue(Context context) {
+        mUseInstallQueue = false;
+        flushInstallQueue(context);
+    }
+    static void flushInstallQueue(Context context) {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
+        if (!installQueue.isEmpty()) {
+            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
+            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
+            int result = INSTALL_SHORTCUT_SUCCESSFUL;
+            String duplicateName = "";
+            while (iter.hasNext()) {
+                final PendingInstallShortcutInfo pendingInfo = iter.next();
+                //final Intent data = pendingInfo.data;
+                final Intent intent = pendingInfo.launchIntent;
+                final String name = pendingInfo.name;
+                final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+                //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
+
+                // TODO-XXX: Disable duplicates for now
+                if (!exists /* && allowDuplicate */) {
+                    // Generate a shortcut info to add into the model
+                    ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
+                            pendingInfo.launchIntent);
+                    addShortcuts.add(info);
+                }
+                /*
+                else if (exists && !allowDuplicate) {
+                    result = INSTALL_SHORTCUT_IS_DUPLICATE;
+                    duplicateName = name;
+                }
+                */
+            }
+
+            // Notify the user once if we weren't able to place any duplicates
+            if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
+                Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
+                        duplicateName), Toast.LENGTH_SHORT).show();
+            }
+
+            // Add the new apps to the model and bind them
+            if (!addShortcuts.isEmpty()) {
+                LauncherAppState app = LauncherAppState.getInstance();
+                app.getModel().addAndBindAddedApps(context, addShortcuts, null);
+            }
+        }
+    }
+
+    private static ShortcutInfo getShortcutInfo(Context context, Intent data,
+                                                Intent launchIntent) {
+        if (launchIntent.getAction() == null) {
+            launchIntent.setAction(Intent.ACTION_VIEW);
+        } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+                launchIntent.getCategories() != null &&
+                launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+            launchIntent.addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        }
+        LauncherAppState app = LauncherAppState.getInstance();
+        return app.getModel().infoFromShortcutIntent(context, data, null);
+    }
+}
diff --git a/src/com/android/launcher3/InstallWidgetReceiver.java b/src/com/android/launcher3/InstallWidgetReceiver.java
new file mode 100644
index 0000000..0ef4780
--- /dev/null
+++ b/src/com/android/launcher3/InstallWidgetReceiver.java
@@ -0,0 +1,192 @@
+/*
+ * 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.launcher3;
+
+import java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+
+/**
+ * We will likely flesh this out later, to handle allow external apps to place widgets, but for now,
+ * we just want to expose the action around for checking elsewhere.
+ */
+public class InstallWidgetReceiver {
+    public static final String ACTION_INSTALL_WIDGET =
+            "com.android.launcher3.action.INSTALL_WIDGET";
+    public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE =
+            "com.android.launcher3.action.SUPPORTS_CLIPDATA_MIMETYPE";
+
+    // Currently not exposed.  Put into Intent when we want to make it public.
+    // TEMP: Should we call this "EXTRA_APPWIDGET_PROVIDER"?
+    public static final String EXTRA_APPWIDGET_COMPONENT =
+        "com.android.launcher3.extra.widget.COMPONENT";
+    public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE =
+        "com.android.launcher3.extra.widget.CONFIGURATION_DATA_MIME_TYPE";
+    public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA =
+        "com.android.launcher3.extra.widget.CONFIGURATION_DATA";
+
+    /**
+     * A simple data class that contains per-item information that the adapter below can reference.
+     */
+    public static class WidgetMimeTypeHandlerData {
+        public ResolveInfo resolveInfo;
+        public AppWidgetProviderInfo widgetInfo;
+
+        public WidgetMimeTypeHandlerData(ResolveInfo rInfo, AppWidgetProviderInfo wInfo) {
+            resolveInfo = rInfo;
+            widgetInfo = wInfo;
+        }
+    }
+
+    /**
+     * The ListAdapter which presents all the valid widgets that can be created for a given drop.
+     */
+    public static class WidgetListAdapter implements ListAdapter, DialogInterface.OnClickListener {
+        private LayoutInflater mInflater;
+        private Launcher mLauncher;
+        private String mMimeType;
+        private ClipData mClipData;
+        private List<WidgetMimeTypeHandlerData> mActivities;
+        private int mTargetLayoutScreen;
+        private int[] mTargetLayoutPos;
+
+        public WidgetListAdapter(Launcher l, String mimeType, ClipData data,
+                List<WidgetMimeTypeHandlerData> list, int targetScreen, int[] targetPos) {
+            mLauncher = l;
+            mMimeType = mimeType;
+            mClipData = data;
+            mActivities = list;
+            mTargetLayoutScreen = targetScreen;
+            mTargetLayoutPos = targetPos;
+        }
+
+        @Override
+        public void registerDataSetObserver(DataSetObserver observer) {
+        }
+
+        @Override
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+        }
+
+        @Override
+        public int getCount() {
+            return mActivities.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final Context context = parent.getContext();
+            final PackageManager packageManager = context.getPackageManager();
+
+            // Lazy-create inflater
+            if (mInflater == null) {
+                mInflater = LayoutInflater.from(context);
+            }
+
+            // Use the convert-view where possible
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.external_widget_drop_list_item, parent,
+                        false);
+            }
+
+            final WidgetMimeTypeHandlerData data = mActivities.get(position);
+            final ResolveInfo resolveInfo = data.resolveInfo;
+            final AppWidgetProviderInfo widgetInfo = data.widgetInfo;
+
+            // Set the icon
+            Drawable d = resolveInfo.loadIcon(packageManager);
+            ImageView i = (ImageView) convertView.findViewById(R.id.provider_icon);
+            i.setImageDrawable(d);
+
+            // Set the text
+            final CharSequence component = resolveInfo.loadLabel(packageManager);
+            final int[] widgetSpan = new int[2];
+            CellLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, widgetSpan);
+            TextView t = (TextView) convertView.findViewById(R.id.provider);
+            t.setText(context.getString(R.string.external_drop_widget_pick_format,
+                    component, widgetSpan[0], widgetSpan[1]));
+
+            return convertView;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return mActivities.isEmpty();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return true;
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            final AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo;
+
+            final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType,
+                    mClipData);
+            mLauncher.addAppWidgetFromDrop(createInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                    mTargetLayoutScreen, null, null, mTargetLayoutPos);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
new file mode 100644
index 0000000..2898b34
--- /dev/null
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -0,0 +1,131 @@
+/*
+ * 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.View;
+
+/**
+ * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation.
+ * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get
+ * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will
+ * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the
+ * interpolator in the same direction.
+ */
+public class InterruptibleInOutAnimator {
+    private long mOriginalDuration;
+    private float mOriginalFromValue;
+    private float mOriginalToValue;
+    private ValueAnimator mAnimator;
+
+    private boolean mFirstRun = true;
+
+    private Object mTag = null;
+
+    private static final int STOPPED = 0;
+    private static final int IN = 1;
+    private static final int OUT = 2;
+
+    // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz
+    private int mDirection = STOPPED;
+
+    public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
+        mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
+        mOriginalDuration = duration;
+        mOriginalFromValue = fromValue;
+        mOriginalToValue = toValue;
+
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mDirection = STOPPED;
+            }
+        });
+    }
+
+    private void animate(int direction) {
+        final long currentPlayTime = mAnimator.getCurrentPlayTime();
+        final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
+        final float startValue = mFirstRun ? mOriginalFromValue :
+                ((Float) mAnimator.getAnimatedValue()).floatValue();
+
+        // Make sure it's stopped before we modify any values
+        cancel();
+
+        // TODO: We don't really need to do the animation if startValue == toValue, but
+        // somehow that doesn't seem to work, possibly a quirk of the animation framework
+        mDirection = direction;
+
+        // Ensure we don't calculate a non-sensical duration
+        long duration = mOriginalDuration - currentPlayTime;
+        mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));
+
+        mAnimator.setFloatValues(startValue, toValue);
+        mAnimator.start();
+        mFirstRun = false;
+    }
+
+    public void cancel() {
+        mAnimator.cancel();
+        mDirection = STOPPED;
+    }
+
+    public void end() {
+        mAnimator.end();
+        mDirection = STOPPED;
+    }
+
+    /**
+     * Return true when the animation is not running and it hasn't even been started.
+     */
+    public boolean isStopped() {
+        return mDirection == STOPPED;
+    }
+
+    /**
+     * This is the equivalent of calling Animator.start(), except that it can be called when
+     * the animation is running in the opposite direction, in which case we reverse
+     * direction and animate for a correspondingly shorter duration.
+     */
+    public void animateIn() {
+        animate(IN);
+    }
+
+    /**
+     * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the
+     * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(),
+     * if the animation is currently running in the opposite direction, we reverse
+     * direction and animate for a correspondingly shorter duration.
+     */
+    public void animateOut() {
+        animate(OUT);
+    }
+
+    public void setTag(Object tag) {
+        mTag = tag;
+    }
+
+    public Object getTag() {
+        return mTag;
+    }
+
+    public ValueAnimator getAnimator() {
+        return mAnimator;
+    }
+}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
new file mode 100644
index 0000000..8c4cefd
--- /dev/null
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Represents an item in the launcher.
+ */
+class ItemInfo {
+    
+    static final int NO_ID = -1;
+    
+    /**
+     * The id in the settings database for this item
+     */
+    long id = NO_ID;
+    
+    /**
+     * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, or
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}.
+     */
+    int itemType;
+    
+    /**
+     * The id of the container that holds this item. For the desktop, this will be 
+     * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it
+     * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
+     * it will be the id of the folder.
+     */
+    long container = NO_ID;
+    
+    /**
+     * Iindicates the screen in which the shortcut appears.
+     */
+    long screenId = -1;
+    
+    /**
+     * Indicates the X position of the associated cell.
+     */
+    int cellX = -1;
+
+    /**
+     * Indicates the Y position of the associated cell.
+     */
+    int cellY = -1;
+
+    /**
+     * Indicates the X cell span.
+     */
+    int spanX = 1;
+
+    /**
+     * Indicates the Y cell span.
+     */
+    int spanY = 1;
+
+    /**
+     * Indicates the minimum X cell span.
+     */
+    int minSpanX = 1;
+
+    /**
+     * Indicates the minimum Y cell span.
+     */
+    int minSpanY = 1;
+
+    /**
+     * Indicates that this item needs to be updated in the db
+     */
+    boolean requiresDbUpdate = false;
+
+    /**
+     * Title of the item
+     */
+    CharSequence title;
+
+    /**
+     * The position of the item in a drag-and-drop operation.
+     */
+    int[] dropPos = null;
+
+    ItemInfo() {
+    }
+
+    ItemInfo(ItemInfo info) {
+        id = info.id;
+        cellX = info.cellX;
+        cellY = info.cellY;
+        spanX = info.spanX;
+        spanY = info.spanY;
+        screenId = info.screenId;
+        itemType = info.itemType;
+        container = info.container;
+        // tempdebug:
+        LauncherModel.checkItemInfo(this);
+    }
+
+    protected Intent getIntent() {
+        throw new RuntimeException("Unexpected Intent");
+    }
+
+    /**
+     * Write the fields of this item to the DB
+     * 
+     * @param values
+     */
+    void onAddToDatabase(ContentValues values) { 
+        values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+        values.put(LauncherSettings.Favorites.CONTAINER, container);
+        values.put(LauncherSettings.Favorites.SCREEN, screenId);
+        values.put(LauncherSettings.Favorites.CELLX, cellX);
+        values.put(LauncherSettings.Favorites.CELLY, cellY);
+        values.put(LauncherSettings.Favorites.SPANX, spanX);
+        values.put(LauncherSettings.Favorites.SPANY, spanY);
+    }
+
+    void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) {
+        values.put(LauncherSettings.Favorites.CELLX, cellX);
+        values.put(LauncherSettings.Favorites.CELLY, cellY);
+    }
+
+    static byte[] flattenBitmap(Bitmap bitmap) {
+        // Try go guesstimate how much space the icon will take when serialized
+        // to avoid unnecessary allocations/copies during the write.
+        int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+        ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+        try {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+            out.flush();
+            out.close();
+            return out.toByteArray();
+        } catch (IOException e) {
+            Log.w("Favorite", "Could not write icon");
+            return null;
+        }
+    }
+
+    static void writeBitmap(ContentValues values, Bitmap bitmap) {
+        if (bitmap != null) {
+            byte[] data = flattenBitmap(bitmap);
+            values.put(LauncherSettings.Favorites.ICON, data);
+        }
+    }
+
+    /**
+     * It is very important that sub-classes implement this if they contain any references
+     * to the activity (anything in the view hierarchy etc.). If not, leaks can result since
+     * ItemInfo objects persist across rotation and can hence leak by holding stale references
+     * to the old view hierarchy / activity.
+     */
+    void unbind() {
+    }
+
+    @Override
+    public String toString() {
+        return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
+            + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
+            + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
new file mode 100644
index 0000000..ee13f29
--- /dev/null
+++ b/src/com/android/launcher3/Launcher.java
@@ -0,0 +1,4494 @@
+
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.TextKeyListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Advanceable;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Default launcher application.
+ */
+public class Launcher extends Activity
+        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
+                   View.OnTouchListener {
+    static final String TAG = "Launcher";
+    static final boolean LOGD = false;
+
+    static final boolean PROFILE_STARTUP = false;
+    static final boolean DEBUG_WIDGETS = false;
+    static final boolean DEBUG_STRICT_MODE = false;
+    static final boolean DEBUG_RESUME_TIME = false;
+    static final boolean DEBUG_DUMP_LOG = false;
+
+    private static final int REQUEST_CREATE_SHORTCUT = 1;
+    private static final int REQUEST_CREATE_APPWIDGET = 5;
+    private static final int REQUEST_PICK_APPLICATION = 6;
+    private static final int REQUEST_PICK_SHORTCUT = 7;
+    private static final int REQUEST_PICK_APPWIDGET = 9;
+    private static final int REQUEST_PICK_WALLPAPER = 10;
+
+    private static final int REQUEST_BIND_APPWIDGET = 11;
+
+    /**
+     * IntentStarter uses request codes starting with this. This must be greater than all activity
+     * request codes used internally.
+     */
+    protected static final int REQUEST_LAST = 100;
+
+    static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
+
+    static final int SCREEN_COUNT = 5;
+    static final int DEFAULT_SCREEN = 2;
+
+    private static final String PREFERENCES = "launcher.preferences";
+    // To turn on these properties, type
+    // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
+    static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
+    static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
+
+    // The Intent extra that defines whether to ignore the launch animation
+    static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
+            "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
+
+    // Type: int
+    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
+    // Type: int
+    private static final String RUNTIME_STATE = "launcher.state";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
+    // Type: boolean
+    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
+    // Type: long
+    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
+    // Type: parcelable
+    private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
+
+    private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
+    private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
+            "com.android.launcher.toolbar_search_icon";
+    private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
+            "com.android.launcher.toolbar_voice_search_icon";
+
+    public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
+    public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
+
+    /** The different states that Launcher can be in. */
+    private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
+    private State mState = State.WORKSPACE;
+    private AnimatorSet mStateAnimation;
+
+    static final int APPWIDGET_HOST_ID = 1024;
+    private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+    private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
+    private static final int SHOW_CLING_DURATION = 250;
+    private static final int DISMISS_CLING_DURATION = 200;
+
+    private static final Object sLock = new Object();
+    private static int sScreen = DEFAULT_SCREEN;
+
+    // How long to wait before the new-shortcut animation automatically pans the workspace
+    private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
+    private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
+    private static int NEW_APPS_ANIMATION_DELAY = 500;
+
+    private final BroadcastReceiver mCloseSystemDialogsReceiver
+            = new CloseSystemDialogsIntentReceiver();
+    private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
+
+    private LayoutInflater mInflater;
+
+    private Workspace mWorkspace;
+    private View mLauncherView;
+    private DragLayer mDragLayer;
+    private DragController mDragController;
+    private View mWeightWatcher;
+
+    private AppWidgetManager mAppWidgetManager;
+    private LauncherAppWidgetHost mAppWidgetHost;
+
+    private ItemInfo mPendingAddInfo = new ItemInfo();
+    private AppWidgetProviderInfo mPendingAddWidgetInfo;
+
+    private int[] mTmpAddItemCellCoordinates = new int[2];
+
+    private FolderInfo mFolderInfo;
+
+    private Hotseat mHotseat;
+    private View mOverviewPanel;
+
+    private View mAllAppsButton;
+
+    private SearchDropTargetBar mSearchDropTargetBar;
+    private AppsCustomizeTabHost mAppsCustomizeTabHost;
+    private AppsCustomizePagedView mAppsCustomizeContent;
+    private boolean mAutoAdvanceRunning = false;
+    private View mQsbBar;
+
+    private Bundle mSavedState;
+    // We set the state in both onCreate and then onNewIntent in some cases, which causes both
+    // scroll issues (because the workspace may not have been measured yet) and extra work.
+    // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
+    private State mOnResumeState = State.NONE;
+
+    private SpannableStringBuilder mDefaultKeySsb = null;
+
+    private boolean mWorkspaceLoading = true;
+
+    private boolean mPaused = true;
+    private boolean mRestoring;
+    private boolean mWaitingForResult;
+    private boolean mOnResumeNeedsLoad;
+
+    private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
+    private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
+
+    // Keep track of whether the user has left launcher
+    private static boolean sPausedFromUserAction = false;
+
+    private Bundle mSavedInstanceState;
+
+    private LauncherModel mModel;
+    private IconCache mIconCache;
+    private boolean mUserPresent = true;
+    private boolean mVisible = false;
+    private boolean mHasFocus = false;
+    private boolean mAttached = false;
+    private static final boolean DISABLE_CLINGS = false;
+    private static final boolean DISABLE_CUSTOM_CLINGS = true;
+
+    private static LocaleConfiguration sLocaleConfiguration = null;
+
+    private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+
+    private View.OnTouchListener mHapticFeedbackTouchListener;
+
+    // Related to the auto-advancing of widgets
+    private final int ADVANCE_MSG = 1;
+    private final int mAdvanceInterval = 20000;
+    private final int mAdvanceStagger = 250;
+    private long mAutoAdvanceSentTime;
+    private long mAutoAdvanceTimeLeft = -1;
+    private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
+        new HashMap<View, AppWidgetProviderInfo>();
+
+    // Determines how long to wait after a rotation before restoring the screen orientation to
+    // match the sensor state.
+    private final int mRestoreScreenOrientationDelay = 500;
+
+    // External icons saved in case of resource changes, orientation, etc.
+    private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
+    private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
+    private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
+
+    private Intent mAppMarketIntent = null;
+    private static final boolean DISABLE_MARKET_BUTTON = true;
+
+    private Drawable mWorkspaceBackgroundDrawable;
+
+    private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
+
+    static final ArrayList<String> sDumpLogs = new ArrayList<String>();
+    static Date sDateStamp = new Date();
+    static DateFormat sDateFormat =
+            DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+    static long sRunStart = System.currentTimeMillis();
+    static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
+
+    // We only want to get the SharedPreferences once since it does an FS stat each time we get
+    // it from the context.
+    private SharedPreferences mSharedPrefs;
+
+    private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
+
+    // Holds the page that we need to animate to, and the icon views that we need to animate up
+    // when we scroll to that page on resume.
+    private ImageView mFolderIconImageView;
+    private Bitmap mFolderIconBitmap;
+    private Canvas mFolderIconCanvas;
+    private Rect mRectForFolderAnimation = new Rect();
+
+    private BubbleTextView mWaitingForResume;
+
+    private HideFromAccessibilityHelper mHideFromAccessibilityHelper
+        = new HideFromAccessibilityHelper();
+
+    private Runnable mBuildLayersRunnable = new Runnable() {
+        public void run() {
+            if (mWorkspace != null) {
+                mWorkspace.buildPageHardwareLayers();
+            }
+        }
+    };
+
+    private static ArrayList<PendingAddArguments> sPendingAddList
+            = new ArrayList<PendingAddArguments>();
+
+    public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
+
+    private static class PendingAddArguments {
+        int requestCode;
+        Intent intent;
+        long container;
+        long screenId;
+        int cellX;
+        int cellY;
+    }
+
+    private Stats mStats;
+
+    private static boolean isPropertyEnabled(String propertyName) {
+        return Log.isLoggable(propertyName, Log.VERBOSE);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (DEBUG_STRICT_MODE) {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectDiskReads()
+                    .detectDiskWrites()
+                    .detectNetwork()   // or .detectAll() for all detectable problems
+                    .penaltyLog()
+                    .build());
+            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+                    .detectLeakedSqlLiteObjects()
+                    .detectLeakedClosableObjects()
+                    .penaltyLog()
+                    .penaltyDeath()
+                    .build());
+        }
+
+        super.onCreate(savedInstanceState);
+
+        LauncherAppState.setApplicationContext(getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+
+        // Determine the dynamic grid properties
+        Point smallestSize = new Point();
+        Point largestSize = new Point();
+        Point realSize = new Point();
+        Display display = getWindowManager().getDefaultDisplay();
+        display.getCurrentSizeRange(smallestSize, largestSize);
+        display.getRealSize(realSize);
+        DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+        // Lazy-initialize the dynamic grid
+        DeviceProfile grid = app.initDynamicGrid(this,
+                Math.min(smallestSize.x, smallestSize.y),
+                Math.min(largestSize.x, largestSize.y),
+                realSize.x, realSize.y,
+                dm.widthPixels, dm.heightPixels);
+
+        // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
+        mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+                Context.MODE_PRIVATE);
+        mModel = app.setLauncher(this);
+        mIconCache = app.getIconCache();
+        mDragController = new DragController(this);
+        mInflater = getLayoutInflater();
+
+        mStats = new Stats(this);
+
+        mAppWidgetManager = AppWidgetManager.getInstance(this);
+
+        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
+        mAppWidgetHost.startListening();
+
+        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
+        // this also ensures that any synchronous binding below doesn't re-trigger another
+        // LauncherModel load.
+        mPaused = false;
+
+        if (PROFILE_STARTUP) {
+            android.os.Debug.startMethodTracing(
+                    Environment.getExternalStorageDirectory() + "/launcher");
+        }
+
+
+        checkForLocaleChange();
+        setContentView(R.layout.launcher);
+
+        setupViews();
+        grid.layout(this);
+
+        registerContentObservers();
+
+        lockAllApps();
+
+        mSavedState = savedInstanceState;
+        restoreState(mSavedState);
+
+        // Update customization drawer _after_ restoring the states
+        if (mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.onPackagesUpdated(
+                LauncherModel.getSortedWidgetsAndShortcuts(this));
+        }
+
+        if (PROFILE_STARTUP) {
+            android.os.Debug.stopMethodTracing();
+        }
+
+        if (!mRestoring) {
+            if (sPausedFromUserAction) {
+                // If the user leaves launcher, then we should just load items asynchronously when
+                // they return.
+                mModel.startLoader(true, -1);
+            } else {
+                // We only load the page synchronously if the user rotates (or triggers a
+                // configuration change) while launcher is in the foreground
+                mModel.startLoader(true, mWorkspace.getCurrentPage());
+            }
+        }
+
+        // For handling default keys
+        mDefaultKeySsb = new SpannableStringBuilder();
+        Selection.setSelection(mDefaultKeySsb, 0);
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        registerReceiver(mCloseSystemDialogsReceiver, filter);
+
+        updateGlobalIcons();
+
+        // On large interfaces, we want the screen to auto-rotate based on the current orientation
+        unlockScreenOrientation(true);
+
+        showFirstRunCling();
+    }
+
+    protected void onUserLeaveHint() {
+        super.onUserLeaveHint();
+        sPausedFromUserAction = true;
+    }
+
+    /** To be overriden by subclasses to hint to Launcher that we have custom content */
+    protected boolean hasCustomContentToLeft() {
+        return false;
+    }
+
+    /**
+     * To be overridden by subclasses to create the custom content and call
+     * {@link #addToCustomContentPage}. This will only be invoked if
+     * {@link #hasCustomContentToLeft()} is {@code true}.
+     */
+    protected void addCustomContentToLeft() {
+    }
+
+    /**
+     * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+     * ensure the custom content page is added or removed if necessary.
+     */
+    protected void invalidateHasCustomContentToLeft() {
+        if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
+            // Not bound yet, wait for bindScreens to be called.
+            return;
+        }
+
+        if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
+            // Create the custom content page and call the subclass to populate it.
+            mWorkspace.createCustomContentPage();
+            addCustomContentToLeft();
+        } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
+            mWorkspace.removeCustomContentPage();
+        }
+    }
+
+    private void updateGlobalIcons() {
+        boolean searchVisible = false;
+        boolean voiceVisible = false;
+        // If we have a saved version of these external icons, we load them up immediately
+        int coi = getCurrentOrientationIndexForGlobalIcons();
+        if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
+                sAppMarketIcon[coi] == null) {
+            if (!DISABLE_MARKET_BUTTON) {
+                updateAppMarketIcon();
+            }
+            searchVisible = updateGlobalSearchIcon();
+            voiceVisible = updateVoiceSearchIcon(searchVisible);
+        }
+        if (sGlobalSearchIcon[coi] != null) {
+             updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
+             searchVisible = true;
+        }
+        if (sVoiceSearchIcon[coi] != null) {
+            updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
+            voiceVisible = true;
+        }
+        if (!DISABLE_MARKET_BUTTON && sAppMarketIcon[coi] != null) {
+            updateAppMarketIcon(sAppMarketIcon[coi]);
+        }
+        if (mSearchDropTargetBar != null) {
+            mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+        }
+    }
+
+    private void checkForLocaleChange() {
+        if (sLocaleConfiguration == null) {
+            new AsyncTask<Void, Void, LocaleConfiguration>() {
+                @Override
+                protected LocaleConfiguration doInBackground(Void... unused) {
+                    LocaleConfiguration localeConfiguration = new LocaleConfiguration();
+                    readConfiguration(Launcher.this, localeConfiguration);
+                    return localeConfiguration;
+                }
+
+                @Override
+                protected void onPostExecute(LocaleConfiguration result) {
+                    sLocaleConfiguration = result;
+                    checkForLocaleChange();  // recursive, but now with a locale configuration
+                }
+            }.execute();
+            return;
+        }
+
+        final Configuration configuration = getResources().getConfiguration();
+
+        final String previousLocale = sLocaleConfiguration.locale;
+        final String locale = configuration.locale.toString();
+
+        final int previousMcc = sLocaleConfiguration.mcc;
+        final int mcc = configuration.mcc;
+
+        final int previousMnc = sLocaleConfiguration.mnc;
+        final int mnc = configuration.mnc;
+
+        boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
+
+        if (localeChanged) {
+            sLocaleConfiguration.locale = locale;
+            sLocaleConfiguration.mcc = mcc;
+            sLocaleConfiguration.mnc = mnc;
+
+            mIconCache.flush();
+
+            final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
+            new Thread("WriteLocaleConfiguration") {
+                @Override
+                public void run() {
+                    writeConfiguration(Launcher.this, localeConfiguration);
+                }
+            }.start();
+        }
+    }
+
+    private static class LocaleConfiguration {
+        public String locale;
+        public int mcc = -1;
+        public int mnc = -1;
+    }
+
+    private static void readConfiguration(Context context, LocaleConfiguration configuration) {
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(context.openFileInput(PREFERENCES));
+            configuration.locale = in.readUTF();
+            configuration.mcc = in.readInt();
+            configuration.mnc = in.readInt();
+        } catch (FileNotFoundException e) {
+            // Ignore
+        } catch (IOException e) {
+            // Ignore
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
+        DataOutputStream out = null;
+        try {
+            out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
+            out.writeUTF(configuration.locale);
+            out.writeInt(configuration.mcc);
+            out.writeInt(configuration.mnc);
+            out.flush();
+        } catch (FileNotFoundException e) {
+            // Ignore
+        } catch (IOException e) {
+            //noinspection ResultOfMethodCallIgnored
+            context.getFileStreamPath(PREFERENCES).delete();
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    public Stats getStats() {
+        return mStats;
+    }
+
+    public LayoutInflater getInflater() {
+        return mInflater;
+    }
+
+    public DragLayer getDragLayer() {
+        return mDragLayer;
+    }
+
+    boolean isDraggingEnabled() {
+        // We prevent dragging when we are loading the workspace as it is possible to pick up a view
+        // that is subsequently removed from the workspace in startBinding().
+        return !mModel.isLoadingWorkspace();
+    }
+
+    static int getScreen() {
+        synchronized (sLock) {
+            return sScreen;
+        }
+    }
+
+    static void setScreen(int screen) {
+        synchronized (sLock) {
+            sScreen = screen;
+        }
+    }
+
+    /**
+     * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
+     * a configuration step, this allows the proper animations to run after other transitions.
+     */
+    private boolean completeAdd(PendingAddArguments args) {
+        boolean result = false;
+        switch (args.requestCode) {
+            case REQUEST_PICK_APPLICATION:
+                completeAddApplication(args.intent, args.container, args.screenId, args.cellX,
+                        args.cellY);
+                break;
+            case REQUEST_PICK_SHORTCUT:
+                processShortcut(args.intent);
+                break;
+            case REQUEST_CREATE_SHORTCUT:
+                completeAddShortcut(args.intent, args.container, args.screenId, args.cellX,
+                        args.cellY);
+                result = true;
+                break;
+            case REQUEST_CREATE_APPWIDGET:
+                int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+                completeAddAppWidget(appWidgetId, args.container, args.screenId, null, null);
+                result = true;
+                break;
+        }
+        // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
+        // if you turned the screen off and then back while in All Apps, Launcher would not
+        // return to the workspace. Clearing mAddInfo.container here fixes this issue
+        resetAddInfo();
+        return result;
+    }
+
+    @Override
+    protected void onActivityResult(
+            final int requestCode, final int resultCode, final Intent data) {
+        // Reset the startActivity waiting flag
+        mWaitingForResult = false;
+
+        if (requestCode == REQUEST_BIND_APPWIDGET) {
+            int appWidgetId = data != null ?
+                    data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+            if (resultCode == RESULT_CANCELED) {
+                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+            } else if (resultCode == RESULT_OK) {
+                addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
+            }
+            return;
+        } else if (requestCode == REQUEST_PICK_WALLPAPER) {
+            if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+                mWorkspace.exitOverviewMode(false);
+            }
+            return;
+        }
+
+        boolean delayExitSpringLoadedMode = false;
+        boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
+                requestCode == REQUEST_CREATE_APPWIDGET);
+
+        // We have special handling for widgets
+        if (isWidgetDrop) {
+            int appWidgetId = data != null ?
+                    data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+            if (appWidgetId < 0) {
+                Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
+                        "widget configuration activity.");
+                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+                mWorkspace.stripEmptyScreens();
+            } else {
+                completeTwoStageWidgetDrop(resultCode, appWidgetId);
+            }
+            return;
+        }
+
+        // The pattern used here is that a user PICKs a specific application,
+        // which, depending on the target, might need to CREATE the actual target.
+
+        // For example, the user would PICK_SHORTCUT for "Music playlist", and we
+        // launch over to the Music app to actually CREATE_SHORTCUT.
+        if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
+            final PendingAddArguments args = new PendingAddArguments();
+            args.requestCode = requestCode;
+            args.intent = data;
+            args.container = mPendingAddInfo.container;
+            args.screenId = mPendingAddInfo.screenId;
+            args.cellX = mPendingAddInfo.cellX;
+            args.cellY = mPendingAddInfo.cellY;
+            if (isWorkspaceLocked()) {
+                sPendingAddList.add(args);
+            } else {
+                delayExitSpringLoadedMode = completeAdd(args);
+            }
+        } else if (resultCode == RESULT_CANCELED) {
+            mWorkspace.stripEmptyScreens();
+        }
+        mDragLayer.clearAnimatedView();
+        // Exit spring loaded mode if necessary after cancelling the configuration of a widget
+        exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
+                null);
+    }
+
+    private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
+        CellLayout cellLayout =
+                (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+        Runnable onCompleteRunnable = null;
+        int animationType = 0;
+
+        AppWidgetHostView boundWidget = null;
+        if (resultCode == RESULT_OK) {
+            animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
+            final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
+                    mPendingAddWidgetInfo);
+            boundWidget = layout;
+            onCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
+                            mPendingAddInfo.screenId, layout, null);
+                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+                            null);
+                }
+            };
+        } else if (resultCode == RESULT_CANCELED) {
+            animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
+            onCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+                            null);
+                }
+            };
+        }
+        if (mDragLayer.getAnimatedView() != null) {
+            mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
+                    (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
+                    animationType, boundWidget, true);
+        } else {
+            // The animated view may be null in the case of a rotation during widget configuration
+            onCompleteRunnable.run();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        FirstFrameAnimatorHelper.setIsVisible(false);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        FirstFrameAnimatorHelper.setIsVisible(true);
+    }
+
+    @Override
+    protected void onResume() {
+        long startTime = 0;
+        if (DEBUG_RESUME_TIME) {
+            startTime = System.currentTimeMillis();
+            Log.v(TAG, "Launcher.onResume()");
+        }
+        super.onResume();
+
+        // Restore the previous launcher state
+        if (mOnResumeState == State.WORKSPACE) {
+            showWorkspace(false);
+        } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
+            showAllApps(false, AppsCustomizePagedView.ContentType.Applications, false);
+        }
+        mOnResumeState = State.NONE;
+
+        // Background was set to gradient in onPause(), restore to black if in all apps.
+        setWorkspaceBackground(mState == State.WORKSPACE);
+
+        mPaused = false;
+        sPausedFromUserAction = false;
+        if (mRestoring || mOnResumeNeedsLoad) {
+            mWorkspaceLoading = true;
+            mModel.startLoader(true, -1);
+            mRestoring = false;
+            mOnResumeNeedsLoad = false;
+        }
+        if (mBindOnResumeCallbacks.size() > 0) {
+            // We might have postponed some bind calls until onResume (see waitUntilResume) --
+            // execute them here
+            long startTimeCallbacks = 0;
+            if (DEBUG_RESUME_TIME) {
+                startTimeCallbacks = System.currentTimeMillis();
+            }
+
+            if (mAppsCustomizeContent != null) {
+                mAppsCustomizeContent.setBulkBind(true);
+            }
+            for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
+                mBindOnResumeCallbacks.get(i).run();
+            }
+            if (mAppsCustomizeContent != null) {
+                mAppsCustomizeContent.setBulkBind(false);
+            }
+            mBindOnResumeCallbacks.clear();
+            if (DEBUG_RESUME_TIME) {
+                Log.d(TAG, "Time spent processing callbacks in onResume: " +
+                    (System.currentTimeMillis() - startTimeCallbacks));
+            }
+        }
+        if (mOnResumeCallbacks.size() > 0) {
+            for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
+                mOnResumeCallbacks.get(i).run();
+            }
+            mOnResumeCallbacks.clear();
+        }
+
+        // Reset the pressed state of icons that were locked in the press state while activities
+        // were launching
+        if (mWaitingForResume != null) {
+            // Resets the previous workspace icon press state
+            mWaitingForResume.setStayPressed(false);
+        }
+        if (mAppsCustomizeContent != null) {
+            // Resets the previous all apps icon press state
+            mAppsCustomizeContent.resetDrawableState();
+        }
+
+        // It is possible that widgets can receive updates while launcher is not in the foreground.
+        // Consequently, the widgets will be inflated in the orientation of the foreground activity
+        // (framework issue). On resuming, we ensure that any widgets are inflated for the current
+        // orientation.
+        getWorkspace().reinflateWidgetsIfNecessary();
+
+        // Process any items that were added while Launcher was away.
+        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+
+        // Update the voice search button proxy
+        updateVoiceButtonProxyVisible(false);
+
+        // Again, as with the above scenario, it's possible that one or more of the global icons
+        // were updated in the wrong orientation.
+        updateGlobalIcons();
+        if (DEBUG_RESUME_TIME) {
+            Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
+        }
+
+        if (mWorkspace.getCustomContentCallbacks() != null) {
+            // If we are resuming and the custom content is the current page, we call onShow().
+            // It is also poassible that onShow will instead be called slightly after first layout
+            // if PagedView#setRestorePage was set to the custom content page in onCreate().
+            if (mWorkspace.isOnOrMovingToCustomContent()) {
+                mWorkspace.getCustomContentCallbacks().onShow();
+            }
+        }
+        mWorkspace.updateInteractionForState();
+        mWorkspace.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Ensure that items added to Launcher are queued until Launcher returns
+        InstallShortcutReceiver.enableInstallQueue();
+
+        super.onPause();
+        mPaused = true;
+        mDragController.cancelDrag();
+        mDragController.resetLastGestureUpTime();
+
+        // We call onHide() aggressively. The custom content callbacks should be able to
+        // debounce excess onHide calls.
+        if (mWorkspace.getCustomContentCallbacks() != null) {
+            mWorkspace.getCustomContentCallbacks().onHide();
+        }
+    }
+
+    protected void onFinishBindingItems() {
+        if (mWorkspace != null && hasCustomContentToLeft() && mWorkspace.hasCustomContent()) {
+            addCustomContentToLeft();
+        }
+    }
+
+    QSBScroller mQsbScroller = new QSBScroller() {
+        int scrollY = 0;
+
+        @Override
+        public void setScrollY(int scroll) {
+            scrollY = scroll;
+
+            if (mWorkspace.isOnOrMovingToCustomContent()) {
+                mSearchDropTargetBar.setTranslationY(- scrollY);
+                getQsbBar().setTranslationY(-scrollY);
+            }
+        }
+    };
+
+    public void resetQSBScroll() {
+        mSearchDropTargetBar.animate().translationY(0).start();
+        getQsbBar().animate().translationY(0).start();
+    }
+
+    public interface CustomContentCallbacks {
+        // Custom content is completely shown
+        public void onShow();
+
+        // Custom content is completely hidden
+        public void onHide();
+
+        // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
+        public void onScrollProgressChanged(float progress);
+    }
+
+    protected void startSettings() {
+    }
+
+    public interface QSBScroller {
+        public void setScrollY(int scrollY);
+    }
+
+    public QSBScroller addToCustomContentPage(View customContent,
+            CustomContentCallbacks callbacks, String description) {
+        mWorkspace.addToCustomContentPage(customContent, callbacks, description);
+        return mQsbScroller;
+    }
+
+    // The custom content needs to offset its content to account for the QSB
+    public int getTopOffsetForCustomContent() {
+        return mWorkspace.getPaddingTop();
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        // Flag the loader to stop early before switching
+        mModel.stopLoader();
+        if (mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.surrender();
+        }
+        return Boolean.TRUE;
+    }
+
+    // We can't hide the IME if it was forced open.  So don't bother
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        mHasFocus = hasFocus;
+    }
+
+    private boolean acceptFilter() {
+        final InputMethodManager inputManager = (InputMethodManager)
+                getSystemService(Context.INPUT_METHOD_SERVICE);
+        return !inputManager.isFullscreenMode();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        final int uniChar = event.getUnicodeChar();
+        final boolean handled = super.onKeyDown(keyCode, event);
+        final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
+        if (!handled && acceptFilter() && isKeyNotWhitespace) {
+            boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
+                    keyCode, event);
+            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
+                // something usable has been typed - start a search
+                // the typed text will be retrieved and cleared by
+                // showSearchDialog()
+                // If there are multiple keystrokes before the search dialog takes focus,
+                // onSearchRequested() will be called for every keystroke,
+                // but it is idempotent, so it's fine.
+                return onSearchRequested();
+            }
+        }
+
+        // Eat the long press event so the keyboard doesn't come up.
+        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
+            return true;
+        }
+
+        return handled;
+    }
+
+    private String getTypedText() {
+        return mDefaultKeySsb.toString();
+    }
+
+    private void clearTypedText() {
+        mDefaultKeySsb.clear();
+        mDefaultKeySsb.clearSpans();
+        Selection.setSelection(mDefaultKeySsb, 0);
+    }
+
+    /**
+     * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
+     * State
+     */
+    private static State intToState(int stateOrdinal) {
+        State state = State.WORKSPACE;
+        final State[] stateValues = State.values();
+        for (int i = 0; i < stateValues.length; i++) {
+            if (stateValues[i].ordinal() == stateOrdinal) {
+                state = stateValues[i];
+                break;
+            }
+        }
+        return state;
+    }
+
+    /**
+     * Restores the previous state, if it exists.
+     *
+     * @param savedState The previous state.
+     */
+    private void restoreState(Bundle savedState) {
+        if (savedState == null) {
+            return;
+        }
+
+        State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+        if (state == State.APPS_CUSTOMIZE) {
+            mOnResumeState = State.APPS_CUSTOMIZE;
+        }
+
+        int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
+                PagedView.INVALID_RESTORE_PAGE);
+        if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
+            mWorkspace.setRestorePage(currentScreen);
+        }
+
+        final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
+        final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
+
+        if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
+            mPendingAddInfo.container = pendingAddContainer;
+            mPendingAddInfo.screenId = pendingAddScreen;
+            mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
+            mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
+            mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
+            mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
+            mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
+            mWaitingForResult = true;
+            mRestoring = true;
+        }
+
+        boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
+        if (renameFolder) {
+            long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
+            mFolderInfo = mModel.getFolderById(this, sFolders, id);
+            mRestoring = true;
+        }
+
+        // Restore the AppsCustomize tab
+        if (mAppsCustomizeTabHost != null) {
+            String curTab = savedState.getString("apps_customize_currentTab");
+            if (curTab != null) {
+                mAppsCustomizeTabHost.setContentTypeImmediate(
+                        mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
+                mAppsCustomizeContent.loadAssociatedPages(
+                        mAppsCustomizeContent.getCurrentPage());
+            }
+
+            int currentIndex = savedState.getInt("apps_customize_currentIndex");
+            mAppsCustomizeContent.restorePageForIndex(currentIndex);
+        }
+    }
+
+    /**
+     * Finds all the views we need and configure them properly.
+     */
+    private void setupViews() {
+        final DragController dragController = mDragController;
+
+        mLauncherView = findViewById(R.id.launcher);
+        mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
+        mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
+
+        mLauncherView.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+        mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
+
+        // Setup the drag layer
+        mDragLayer.setup(this, dragController);
+
+        // Setup the hotseat
+        mHotseat = (Hotseat) findViewById(R.id.hotseat);
+        if (mHotseat != null) {
+            mHotseat.setup(this);
+            mHotseat.setOnLongClickListener(this);
+        }
+
+        mOverviewPanel = findViewById(R.id.overview_panel);
+        View widgetButton = findViewById(R.id.widget_button);
+        widgetButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+            }
+        });
+        widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+        View wallpaperButton = findViewById(R.id.wallpaper_button);
+        wallpaperButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                startWallpaper();
+            }
+        });
+        wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+        View settingsButton = findViewById(R.id.settings_button);
+        settingsButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                startSettings();
+            }
+        });
+        settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+        mOverviewPanel.setAlpha(0f);
+
+        // Setup the workspace
+        mWorkspace.setHapticFeedbackEnabled(false);
+        mWorkspace.setOnLongClickListener(this);
+        mWorkspace.setup(dragController);
+        dragController.addDragListener(mWorkspace);
+
+        // Get the search/delete bar
+        mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
+
+        // Setup AppsCustomize
+        mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
+        mAppsCustomizeContent = (AppsCustomizePagedView)
+                mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
+        mAppsCustomizeContent.setup(this, dragController);
+
+        // Setup the drag controller (drop targets have to be added in reverse order in priority)
+        dragController.setDragScoller(mWorkspace);
+        dragController.setScrollView(mDragLayer);
+        dragController.setMoveTarget(mWorkspace);
+        dragController.addDropTarget(mWorkspace);
+        if (mSearchDropTargetBar != null) {
+            mSearchDropTargetBar.setup(this, dragController);
+        }
+
+        if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
+            Log.v(TAG, "adding WeightWatcher");
+            mWeightWatcher = new WeightWatcher(this);
+            mWeightWatcher.setAlpha(0.5f);
+            ((FrameLayout) mLauncherView).addView(mWeightWatcher,
+                    new FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
+                            Gravity.BOTTOM)
+            );
+
+            boolean show = shouldShowWeightWatcher();
+            mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    /**
+     * Creates a view representing a shortcut.
+     *
+     * @param info The data structure describing the shortcut.
+     *
+     * @return A View inflated from R.layout.application.
+     */
+    View createShortcut(ShortcutInfo info) {
+        return createShortcut(R.layout.application,
+                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+    }
+
+    /**
+     * Creates a view representing a shortcut inflated from the specified resource.
+     *
+     * @param layoutResId The id of the XML layout used to create the shortcut.
+     * @param parent The group the shortcut belongs to.
+     * @param info The data structure describing the shortcut.
+     *
+     * @return A View inflated from layoutResId.
+     */
+    View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
+        BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
+        favorite.applyFromShortcutInfo(info, mIconCache);
+        favorite.setOnClickListener(this);
+        return favorite;
+    }
+
+    /**
+     * Add an application shortcut to the workspace.
+     *
+     * @param data The intent describing the application.
+     * @param cellInfo The position on screen where to create the shortcut.
+     */
+    void completeAddApplication(Intent data, long container, long screenId, int cellX, int cellY) {
+        final int[] cellXY = mTmpAddItemCellCoordinates;
+        final CellLayout layout = getCellLayout(container, screenId);
+
+        // First we check if we already know the exact location where we want to add this item.
+        if (cellX >= 0 && cellY >= 0) {
+            cellXY[0] = cellX;
+            cellXY[1] = cellY;
+        } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
+            showOutOfSpaceMessage(isHotseatLayout(layout));
+            return;
+        }
+
+        final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
+
+        if (info != null) {
+            info.setActivity(this, data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
+                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+            info.container = ItemInfo.NO_ID;
+            mWorkspace.addApplicationShortcut(info, layout, container, screenId, cellXY[0], cellXY[1],
+                    isWorkspaceLocked(), cellX, cellY);
+        } else {
+            Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
+        }
+    }
+
+    /**
+     * Add a shortcut to the workspace.
+     *
+     * @param data The intent describing the shortcut.
+     * @param cellInfo The position on screen where to create the shortcut.
+     */
+    private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
+            int cellY) {
+        int[] cellXY = mTmpAddItemCellCoordinates;
+        int[] touchXY = mPendingAddInfo.dropPos;
+        CellLayout layout = getCellLayout(container, screenId);
+
+        boolean foundCellSpan = false;
+
+        ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
+        if (info == null) {
+            return;
+        }
+        final View view = createShortcut(info);
+
+        // First we check if we already know the exact location where we want to add this item.
+        if (cellX >= 0 && cellY >= 0) {
+            cellXY[0] = cellX;
+            cellXY[1] = cellY;
+            foundCellSpan = true;
+
+            // If appropriate, either create a folder or add to an existing folder
+            if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
+                    true, null,null)) {
+                return;
+            }
+            DragObject dragObject = new DragObject();
+            dragObject.dragInfo = info;
+            if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
+                    true)) {
+                return;
+            }
+        } else if (touchXY != null) {
+            // when dragging and dropping, just find the closest free spot
+            int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
+            foundCellSpan = (result != null);
+        } else {
+            foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
+        }
+
+        if (!foundCellSpan) {
+            showOutOfSpaceMessage(isHotseatLayout(layout));
+            return;
+        }
+
+        LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
+
+        if (!mRestoring) {
+            mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
+                    isWorkspaceLocked());
+        }
+    }
+
+    static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
+            int minHeight) {
+        Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
+        // We want to account for the extra amount of padding that we are adding to the widget
+        // to ensure that it gets the full amount of space that it has requested
+        int requiredWidth = minWidth + padding.left + padding.right;
+        int requiredHeight = minHeight + padding.top + padding.bottom;
+        return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
+    }
+
+    static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
+        return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
+    }
+
+    static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
+        return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
+    }
+
+    static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
+        return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
+    }
+
+    static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
+        return getSpanForWidget(context, info.componentName, info.minResizeWidth,
+                info.minResizeHeight);
+    }
+
+    /**
+     * Add a widget to the workspace.
+     *
+     * @param appWidgetId The app widget id
+     * @param cellInfo The position on screen where to create the widget.
+     */
+    private void completeAddAppWidget(final int appWidgetId, long container, long screenId,
+            AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
+        if (appWidgetInfo == null) {
+            appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+        }
+
+        // Calculate the grid spans needed to fit this widget
+        CellLayout layout = getCellLayout(container, screenId);
+
+        int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
+        int[] spanXY = getSpanForWidget(this, appWidgetInfo);
+
+        // Try finding open space on Launcher screen
+        // We have saved the position to which the widget was dragged-- this really only matters
+        // if we are placing widgets on a "spring-loaded" screen
+        int[] cellXY = mTmpAddItemCellCoordinates;
+        int[] touchXY = mPendingAddInfo.dropPos;
+        int[] finalSpan = new int[2];
+        boolean foundCellSpan = false;
+        if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
+            cellXY[0] = mPendingAddInfo.cellX;
+            cellXY[1] = mPendingAddInfo.cellY;
+            spanXY[0] = mPendingAddInfo.spanX;
+            spanXY[1] = mPendingAddInfo.spanY;
+            foundCellSpan = true;
+        } else if (touchXY != null) {
+            // when dragging and dropping, just find the closest free spot
+            int[] result = layout.findNearestVacantArea(
+                    touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
+                    spanXY[1], cellXY, finalSpan);
+            spanXY[0] = finalSpan[0];
+            spanXY[1] = finalSpan[1];
+            foundCellSpan = (result != null);
+        } else {
+            foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
+        }
+
+        if (!foundCellSpan) {
+            if (appWidgetId != -1) {
+                // Deleting an app widget ID is a void call but writes to disk before returning
+                // to the caller...
+                new Thread("deleteAppWidgetId") {
+                    public void run() {
+                        mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+                    }
+                }.start();
+            }
+            showOutOfSpaceMessage(isHotseatLayout(layout));
+            return;
+        }
+
+        // Build Launcher-specific widget info and save to database
+        LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
+                appWidgetInfo.provider);
+        launcherInfo.spanX = spanXY[0];
+        launcherInfo.spanY = spanXY[1];
+        launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
+        launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
+
+        LauncherModel.addItemToDatabase(this, launcherInfo,
+                container, screenId, cellXY[0], cellXY[1], false);
+
+        if (!mRestoring) {
+            if (hostView == null) {
+                // Perform actual inflation because we're live
+                launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+                launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+            } else {
+                // The AppWidgetHostView has already been inflated and instantiated
+                launcherInfo.hostView = hostView;
+            }
+
+            launcherInfo.hostView.setTag(launcherInfo);
+            launcherInfo.hostView.setVisibility(View.VISIBLE);
+            launcherInfo.notifyWidgetSizeChanged(this);
+
+            mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1],
+                    launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
+
+            addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
+        }
+        resetAddInfo();
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mUserPresent = false;
+                mDragLayer.clearAllResizeFrames();
+                updateRunning();
+
+                // Reset AllApps to its initial state only if we are not in the middle of
+                // processing a multi-step drop
+                if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
+                    showWorkspace(false);
+                }
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+                mUserPresent = true;
+                updateRunning();
+            }
+        }
+    };
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Listen for broadcasts related to user-presence
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        registerReceiver(mReceiver, filter);
+        FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
+        mAttached = true;
+        mVisible = true;
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mVisible = false;
+
+        if (mAttached) {
+            unregisterReceiver(mReceiver);
+            mAttached = false;
+        }
+        updateRunning();
+    }
+
+    public void onWindowVisibilityChanged(int visibility) {
+        mVisible = visibility == View.VISIBLE;
+        updateRunning();
+        // The following code used to be in onResume, but it turns out onResume is called when
+        // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
+        // is a more appropriate event to handle
+        if (mVisible) {
+            mAppsCustomizeTabHost.onWindowVisible();
+            if (!mWorkspaceLoading) {
+                final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
+                // We want to let Launcher draw itself at least once before we force it to build
+                // layers on all the workspace pages, so that transitioning to Launcher from other
+                // apps is nice and speedy.
+                observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+                    private boolean mStarted = false;
+                    public void onDraw() {
+                        if (mStarted) return;
+                        mStarted = true;
+                        // We delay the layer building a bit in order to give
+                        // other message processing a time to run.  In particular
+                        // this avoids a delay in hiding the IME if it was
+                        // currently shown, because doing that may involve
+                        // some communication back with the app.
+                        mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+                        final ViewTreeObserver.OnDrawListener listener = this;
+                        mWorkspace.post(new Runnable() {
+                                public void run() {
+                                    if (mWorkspace != null &&
+                                            mWorkspace.getViewTreeObserver() != null) {
+                                        mWorkspace.getViewTreeObserver().
+                                                removeOnDrawListener(listener);
+                                    }
+                                }
+                            });
+                        return;
+                    }
+                });
+            }
+            // When Launcher comes back to foreground, a different Activity might be responsible for
+            // the app market intent, so refresh the icon
+            if (!DISABLE_MARKET_BUTTON) {
+                updateAppMarketIcon();
+            }
+            clearTypedText();
+        }
+    }
+
+    private void sendAdvanceMessage(long delay) {
+        mHandler.removeMessages(ADVANCE_MSG);
+        Message msg = mHandler.obtainMessage(ADVANCE_MSG);
+        mHandler.sendMessageDelayed(msg, delay);
+        mAutoAdvanceSentTime = System.currentTimeMillis();
+    }
+
+    private void updateRunning() {
+        boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
+        if (autoAdvanceRunning != mAutoAdvanceRunning) {
+            mAutoAdvanceRunning = autoAdvanceRunning;
+            if (autoAdvanceRunning) {
+                long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
+                sendAdvanceMessage(delay);
+            } else {
+                if (!mWidgetsToAdvance.isEmpty()) {
+                    mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
+                            (System.currentTimeMillis() - mAutoAdvanceSentTime));
+                }
+                mHandler.removeMessages(ADVANCE_MSG);
+                mHandler.removeMessages(0); // Remove messages sent using postDelayed()
+            }
+        }
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == ADVANCE_MSG) {
+                int i = 0;
+                for (View key: mWidgetsToAdvance.keySet()) {
+                    final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
+                    final int delay = mAdvanceStagger * i;
+                    if (v instanceof Advanceable) {
+                       postDelayed(new Runnable() {
+                           public void run() {
+                               ((Advanceable) v).advance();
+                           }
+                       }, delay);
+                    }
+                    i++;
+                }
+                sendAdvanceMessage(mAdvanceInterval);
+            }
+        }
+    };
+
+    void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
+        if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
+        View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
+        if (v instanceof Advanceable) {
+            mWidgetsToAdvance.put(hostView, appWidgetInfo);
+            ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
+            updateRunning();
+        }
+    }
+
+    void removeWidgetToAutoAdvance(View hostView) {
+        if (mWidgetsToAdvance.containsKey(hostView)) {
+            mWidgetsToAdvance.remove(hostView);
+            updateRunning();
+        }
+    }
+
+    public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
+        removeWidgetToAutoAdvance(launcherInfo.hostView);
+        launcherInfo.hostView = null;
+    }
+
+    void showOutOfSpaceMessage(boolean isHotseatLayout) {
+        int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+        Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
+    }
+
+    public LauncherAppWidgetHost getAppWidgetHost() {
+        return mAppWidgetHost;
+    }
+
+    public LauncherModel getModel() {
+        return mModel;
+    }
+
+    public void closeSystemDialogs() {
+        getWindow().closeAllPanels();
+
+        // Whatever we were doing is hereby canceled.
+        mWaitingForResult = false;
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        long startTime = 0;
+        if (DEBUG_RESUME_TIME) {
+            startTime = System.currentTimeMillis();
+        }
+        super.onNewIntent(intent);
+
+        // Close the menu
+        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+            // also will cancel mWaitingForResult.
+            closeSystemDialogs();
+
+            final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+                    Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+                    != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+
+            if (mWorkspace == null) {
+                // Can be cases where mWorkspace is null, this prevents a NPE
+                return;
+            }
+            Folder openFolder = mWorkspace.getOpenFolder();
+            // In all these cases, only animate if we're already on home
+            mWorkspace.exitWidgetResizeMode();
+            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+                    openFolder == null) {
+                mWorkspace.moveToDefaultScreen(true);
+            }
+
+            closeFolder();
+            exitSpringLoadedDragMode();
+
+            // If we are already on home, then just animate back to the workspace,
+            // otherwise, just wait until onResume to set the state back to Workspace
+            if (alreadyOnHome) {
+                showWorkspace(true);
+            } else {
+                mOnResumeState = State.WORKSPACE;
+            }
+
+            final View v = getWindow().peekDecorView();
+            if (v != null && v.getWindowToken() != null) {
+                InputMethodManager imm = (InputMethodManager)getSystemService(
+                        INPUT_METHOD_SERVICE);
+                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+            }
+
+            // Reset the apps customize page
+            if (mAppsCustomizeTabHost != null) {
+                mAppsCustomizeTabHost.reset();
+            }
+        }
+
+        if (DEBUG_RESUME_TIME) {
+            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+        }
+    }
+
+    @Override
+    public void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        for (int page: mSynchronouslyBoundPages) {
+            mWorkspace.restoreInstanceStateForChild(page);
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        if (mWorkspace.getChildCount() > 0) {
+            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getRestorePage());
+        }
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(RUNTIME_STATE, mState.ordinal());
+        // We close any open folder since it will not be re-opened, and we need to make sure
+        // this state is reflected.
+        closeFolder();
+
+        if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
+                mWaitingForResult) {
+            outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
+            outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
+            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
+            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
+            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
+            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
+            outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
+        }
+
+        if (mFolderInfo != null && mWaitingForResult) {
+            outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
+            outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
+        }
+
+        // Save the current AppsCustomize tab
+        if (mAppsCustomizeTabHost != null) {
+            String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
+            if (currentTabTag != null) {
+                outState.putString("apps_customize_currentTab", currentTabTag);
+            }
+            int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
+            outState.putInt("apps_customize_currentIndex", currentIndex);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        // Remove all pending runnables
+        mHandler.removeMessages(ADVANCE_MSG);
+        mHandler.removeMessages(0);
+        mWorkspace.removeCallbacks(mBuildLayersRunnable);
+
+        // Stop callbacks from LauncherModel
+        LauncherAppState app = (LauncherAppState.getInstance());
+        mModel.stopLoader();
+        app.setLauncher(null);
+
+        try {
+            mAppWidgetHost.stopListening();
+        } catch (NullPointerException ex) {
+            Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
+        }
+        mAppWidgetHost = null;
+
+        mWidgetsToAdvance.clear();
+
+        TextKeyListener.getInstance().release();
+
+        // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
+        // to prevent leaking Launcher activities on orientation change.
+        if (mModel != null) {
+            mModel.unbindItemInfosAndClearQueuedBindRunnables();
+        }
+
+        getContentResolver().unregisterContentObserver(mWidgetObserver);
+        unregisterReceiver(mCloseSystemDialogsReceiver);
+
+        mDragLayer.clearAllResizeFrames();
+        ((ViewGroup) mWorkspace.getParent()).removeAllViews();
+        mWorkspace.removeAllViews();
+        mWorkspace = null;
+        mDragController = null;
+
+        LauncherAnimUtils.onDestroyActivity();
+    }
+
+    public DragController getDragController() {
+        return mDragController;
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        if (requestCode >= 0) mWaitingForResult = true;
+        super.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Indicates that we want global search for this activity by setting the globalSearch
+     * argument for {@link #startSearch} to true.
+     */
+    @Override
+    public void startSearch(String initialQuery, boolean selectInitialQuery,
+            Bundle appSearchData, boolean globalSearch) {
+
+        showWorkspace(true);
+
+        if (initialQuery == null) {
+            // Use any text typed in the launcher as the initial query
+            initialQuery = getTypedText();
+        }
+        if (appSearchData == null) {
+            appSearchData = new Bundle();
+            appSearchData.putString("source", "launcher-search");
+        }
+        Rect sourceBounds = new Rect();
+        if (mSearchDropTargetBar != null) {
+            sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
+        }
+
+        startSearch(initialQuery, selectInitialQuery,
+                appSearchData, sourceBounds);
+    }
+
+    public void startSearch(String initialQuery,
+            boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
+        startGlobalSearch(initialQuery, selectInitialQuery,
+                appSearchData, sourceBounds);
+    }
+
+    /**
+     * Starts the global search activity. This code is a copied from SearchManager
+     */
+    private void startGlobalSearch(String initialQuery,
+            boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
+        final SearchManager searchManager =
+            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
+        if (globalSearchActivity == null) {
+            Log.w(TAG, "No global search activity found.");
+            return;
+        }
+        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setComponent(globalSearchActivity);
+        // Make sure that we have a Bundle to put source in
+        if (appSearchData == null) {
+            appSearchData = new Bundle();
+        } else {
+            appSearchData = new Bundle(appSearchData);
+        }
+        // Set source to package name of app that starts global search, if not set already.
+        if (!appSearchData.containsKey("source")) {
+            appSearchData.putString("source", getPackageName());
+        }
+        intent.putExtra(SearchManager.APP_DATA, appSearchData);
+        if (!TextUtils.isEmpty(initialQuery)) {
+            intent.putExtra(SearchManager.QUERY, initialQuery);
+        }
+        if (selectInitialQuery) {
+            intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
+        }
+        intent.setSourceBounds(sourceBounds);
+        try {
+            startActivity(intent);
+        } catch (ActivityNotFoundException ex) {
+            Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
+        }
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        if (!mWorkspace.isInOverviewMode()) {
+            mWorkspace.enterOverviewMode();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        startSearch(null, false, null, true);
+        // Use a custom animation for launching search
+        return true;
+    }
+
+    public boolean isWorkspaceLocked() {
+        return mWorkspaceLoading || mWaitingForResult;
+    }
+
+    private void resetAddInfo() {
+        mPendingAddInfo.container = ItemInfo.NO_ID;
+        mPendingAddInfo.screenId = -1;
+        mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
+        mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
+        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+        mPendingAddInfo.dropPos = null;
+    }
+
+    void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
+            AppWidgetProviderInfo appWidgetInfo) {
+        if (appWidgetInfo.configure != null) {
+            mPendingAddWidgetInfo = appWidgetInfo;
+
+            // Launch over to configure widget, if needed
+            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+            intent.setComponent(appWidgetInfo.configure);
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+            Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_APPWIDGET);
+        } else {
+            // Otherwise just add it
+            completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
+                    appWidgetInfo);
+            // Exit spring loaded mode if necessary after adding the widget
+            exitSpringLoadedDragModeDelayed(true, false, null);
+        }
+    }
+
+    protected void moveToCustomContentScreen(boolean animate) {
+        // Close any folders that may be open.
+        closeFolder();
+        mWorkspace.moveToCustomContentScreen(animate);
+    }
+    /**
+     * Process a shortcut drop.
+     *
+     * @param componentName The name of the component
+     * @param screenId The ID of the screen where it should be added
+     * @param cell The cell it should be added to, optional
+     * @param position The location on the screen where it was dropped, optional
+     */
+    void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
+            int[] cell, int[] loc) {
+        resetAddInfo();
+        mPendingAddInfo.container = container;
+        mPendingAddInfo.screenId = screenId;
+        mPendingAddInfo.dropPos = loc;
+
+        if (cell != null) {
+            mPendingAddInfo.cellX = cell[0];
+            mPendingAddInfo.cellY = cell[1];
+        }
+
+        Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+        createShortcutIntent.setComponent(componentName);
+        processShortcut(createShortcutIntent);
+    }
+
+    /**
+     * Process a widget drop.
+     *
+     * @param info The PendingAppWidgetInfo of the widget being added.
+     * @param screenId The ID of the screen where it should be added
+     * @param cell The cell it should be added to, optional
+     * @param position The location on the screen where it was dropped, optional
+     */
+    void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
+            int[] cell, int[] span, int[] loc) {
+        resetAddInfo();
+        mPendingAddInfo.container = info.container = container;
+        mPendingAddInfo.screenId = info.screenId = screenId;
+        mPendingAddInfo.dropPos = loc;
+        mPendingAddInfo.minSpanX = info.minSpanX;
+        mPendingAddInfo.minSpanY = info.minSpanY;
+
+        if (cell != null) {
+            mPendingAddInfo.cellX = cell[0];
+            mPendingAddInfo.cellY = cell[1];
+        }
+        if (span != null) {
+            mPendingAddInfo.spanX = span[0];
+            mPendingAddInfo.spanY = span[1];
+        }
+
+        AppWidgetHostView hostView = info.boundWidget;
+        int appWidgetId;
+        if (hostView != null) {
+            appWidgetId = hostView.getAppWidgetId();
+            addAppWidgetImpl(appWidgetId, info, hostView, info.info);
+        } else {
+            // In this case, we either need to start an activity to get permission to bind
+            // the widget, or we need to start an activity to configure the widget, or both.
+            appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+            Bundle options = info.bindOptions;
+
+            boolean success = false;
+            if (options != null) {
+                success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+                        info.componentName, options);
+            } else {
+                success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+                        info.componentName);
+            }
+            if (success) {
+                addAppWidgetImpl(appWidgetId, info, null, info.info);
+            } else {
+                mPendingAddWidgetInfo = info.info;
+                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
+                // TODO: we need to make sure that this accounts for the options bundle.
+                // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+                startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+            }
+        }
+    }
+
+    void processShortcut(Intent intent) {
+        // Handle case where user selected "Applications"
+        String applicationName = getResources().getString(R.string.group_applications);
+        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
+        if (applicationName != null && applicationName.equals(shortcutName)) {
+            Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+            Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+            pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
+            pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
+            Utilities.startActivityForResultSafely(this, pickIntent, REQUEST_PICK_APPLICATION);
+        } else {
+            Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
+        }
+    }
+
+    void processWallpaper(Intent intent) {
+        startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
+    }
+
+    FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
+            int cellY) {
+        final FolderInfo folderInfo = new FolderInfo();
+        folderInfo.title = getText(R.string.folder_name);
+
+        // Update the model
+        LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
+                false);
+        sFolders.put(folderInfo.id, folderInfo);
+
+        // Create the view
+        FolderIcon newFolder =
+            FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
+        mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
+                isWorkspaceLocked());
+        // Force measure the new folder icon
+        CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
+        parent.getShortcutsAndWidgets().measureChild(newFolder);
+        return newFolder;
+    }
+
+    void removeFolder(FolderInfo folder) {
+        sFolders.remove(folder.id);
+    }
+
+    protected void startWallpaper() {
+        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
+        pickWallpaper.setComponent(getWallpaperPickerComponent());
+        startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
+    }
+
+    protected ComponentName getWallpaperPickerComponent() {
+        return new ComponentName(getPackageName(), WallpaperPickerActivity.class.getName());
+    }
+
+    /**
+     * Registers various content observers. The current implementation registers
+     * only a favorites observer to keep track of the favorites applications.
+     */
+    private void registerContentObservers() {
+        ContentResolver resolver = getContentResolver();
+        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
+                true, mWidgetObserver);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_HOME:
+                    return true;
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
+                        dumpState();
+                        return true;
+                    }
+                    break;
+            }
+        } else if (event.getAction() == KeyEvent.ACTION_UP) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_HOME:
+                    return true;
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (isAllAppsVisible()) {
+            if (mAppsCustomizeContent.getContentType() ==
+                    AppsCustomizePagedView.ContentType.Applications) {
+                showWorkspace(true);
+            } else {
+                showOverviewMode(true);
+            }
+        } else if (mWorkspace.isInOverviewMode()) {
+            mWorkspace.exitOverviewMode(true);
+        } else if (mWorkspace.getOpenFolder() != null) {
+            Folder openFolder = mWorkspace.getOpenFolder();
+            if (openFolder.isEditingName()) {
+                openFolder.dismissEditingName();
+            } else {
+                closeFolder();
+            }
+        } else {
+            mWorkspace.exitWidgetResizeMode();
+
+            // Back button is a no-op here, but give at least some feedback for the button press
+            mWorkspace.showOutlinesTemporarily();
+        }
+    }
+
+    /**
+     * Re-listen when widgets are reset.
+     */
+    private void onAppWidgetReset() {
+        if (mAppWidgetHost != null) {
+            mAppWidgetHost.startListening();
+        }
+    }
+
+    /**
+     * Launches the intent referred by the clicked shortcut.
+     *
+     * @param v The view representing the clicked shortcut.
+     */
+    public void onClick(View v) {
+        // Make sure that rogue clicks don't get through while allapps is launching, or after the
+        // view has detached (it's possible for this to happen if the view is removed mid touch).
+        if (v.getWindowToken() == null) {
+            return;
+        }
+
+        if (!mWorkspace.isFinishedSwitchingState()) {
+            return;
+        }
+
+        if (v instanceof Workspace) {
+            if (mWorkspace.isInOverviewMode()) {
+                mWorkspace.exitOverviewMode(true);
+            }
+            return;
+        }
+
+        if (v instanceof CellLayout) {
+            if (mWorkspace.isInOverviewMode()) {
+                mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
+            }
+        }
+
+        Object tag = v.getTag();
+        if (tag instanceof ShortcutInfo) {
+            // Open shortcut
+            final ShortcutInfo shortcut = (ShortcutInfo) tag;
+            final Intent intent = shortcut.intent;
+
+            // Check for special shortcuts
+            if (intent.getComponent() != null) {
+                final String shortcutClass = intent.getComponent().getClassName();
+
+                if (shortcutClass.equals(WidgetAdder.class.getName())) {
+                    showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+                    return;
+                } else if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
+                    MemoryDumpActivity.startDump(this);
+                    return;
+                } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
+                    toggleShowWeightWatcher();
+                    return;
+                }
+            }
+
+            // Start activities
+            int[] pos = new int[2];
+            v.getLocationOnScreen(pos);
+            intent.setSourceBounds(new Rect(pos[0], pos[1],
+                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
+
+            boolean success = startActivitySafely(v, intent, tag);
+
+            mStats.recordLaunch(intent, shortcut);
+
+            if (success && v instanceof BubbleTextView) {
+                mWaitingForResume = (BubbleTextView) v;
+                mWaitingForResume.setStayPressed(true);
+            }
+        } else if (tag instanceof FolderInfo) {
+            if (v instanceof FolderIcon) {
+                FolderIcon fi = (FolderIcon) v;
+                handleFolderClick(fi);
+            }
+        } else if (v == mAllAppsButton) {
+            if (isAllAppsVisible()) {
+                showWorkspace(true);
+            } else {
+                onClickAllAppsButton(v);
+            }
+        }
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Event handler for the search button
+     *
+     * @param v The view that was clicked.
+     */
+    public void onClickSearchButton(View v) {
+        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+        onSearchRequested();
+    }
+
+    /**
+     * Event handler for the voice button
+     *
+     * @param v The view that was clicked.
+     */
+    public void onClickVoiceButton(View v) {
+        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+        startVoice();
+    }
+
+    public void startVoice() {
+        try {
+            final SearchManager searchManager =
+                    (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+            ComponentName activityName = searchManager.getGlobalSearchActivity();
+            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            if (activityName != null) {
+                intent.setPackage(activityName.getPackageName());
+            }
+            startActivity(null, intent, "onClickVoiceButton");
+        } catch (ActivityNotFoundException e) {
+            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivitySafely(null, intent, "onClickVoiceButton");
+        }
+    }
+
+    /**
+     * Event handler for the "grid" button that appears on the home screen, which
+     * enters all apps mode.
+     *
+     * @param v The view that was clicked.
+     */
+    public void onClickAllAppsButton(View v) {
+        showAllApps(true, AppsCustomizePagedView.ContentType.Applications, true);
+    }
+
+    public void onTouchDownAllAppsButton(View v) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+    }
+
+    public void performHapticFeedbackOnTouchDown(View v) {
+        // Provide the same haptic feedback that the system offers for virtual keys.
+        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+    }
+
+    public View.OnTouchListener getHapticFeedbackTouchListener() {
+        if (mHapticFeedbackTouchListener == null) {
+            mHapticFeedbackTouchListener = new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+                        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                    }
+                    return false;
+                }
+            };
+        }
+        return mHapticFeedbackTouchListener;
+    }
+
+    public void onClickAppMarketButton(View v) {
+        if (!DISABLE_MARKET_BUTTON) {
+            if (mAppMarketIntent != null) {
+                startActivitySafely(v, mAppMarketIntent, "app market");
+            } else {
+                Log.e(TAG, "Invalid app market intent.");
+            }
+        }
+    }
+
+    /**
+     * Called when the user stops interacting with the launcher.
+     * This implies that the user is now on the homescreen and is not doing housekeeping.
+     */
+    protected void onInteractionEnd() {}
+
+    /**
+     * Called when the user starts interacting with the launcher.
+     * The possible interactions are:
+     *  - open all apps
+     *  - reorder an app shortcut, or a widget
+     *  - open the overview mode.
+     * This is a good time to stop doing things that only make sense
+     * when the user is on the homescreen and not doing housekeeping.
+     */
+    protected void onInteractionBegin() {}
+
+    void startApplicationDetailsActivity(ComponentName componentName) {
+        String packageName = componentName.getPackageName();
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                Uri.fromParts("package", packageName, null));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        startActivitySafely(null, intent, "startApplicationDetailsActivity");
+    }
+
+    // returns true if the activity was started
+    boolean startApplicationUninstallActivity(ComponentName componentName, int flags) {
+        if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
+            // System applications cannot be installed. For now, show a toast explaining that.
+            // We may give them the option of disabling apps this way.
+            int messageId = R.string.uninstall_system_app_text;
+            Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
+            return false;
+        } else {
+            String packageName = componentName.getPackageName();
+            String className = componentName.getClassName();
+            Intent intent = new Intent(
+                    Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                    Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            startActivity(intent);
+            return true;
+        }
+    }
+
+    boolean startActivity(View v, Intent intent, Object tag) {
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        try {
+            // Only launch using the new animation if the shortcut has not opted out (this is a
+            // private contract between launcher and may be ignored in the future).
+            boolean useLaunchAnimation = (v != null) &&
+                    !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
+            if (useLaunchAnimation) {
+                ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
+                        v.getMeasuredWidth(), v.getMeasuredHeight());
+
+                startActivity(intent, opts.toBundle());
+            } else {
+                startActivity(intent);
+            }
+            return true;
+        } catch (SecurityException e) {
+            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+                    "or use the exported attribute for this activity. "
+                    + "tag="+ tag + " intent=" + intent, e);
+        }
+        return false;
+    }
+
+    boolean startActivitySafely(View v, Intent intent, Object tag) {
+        boolean success = false;
+        try {
+            success = startActivity(v, intent, tag);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
+        }
+        return success;
+    }
+
+    private void handleFolderClick(FolderIcon folderIcon) {
+        final FolderInfo info = folderIcon.getFolderInfo();
+        Folder openFolder = mWorkspace.getFolderForTag(info);
+
+        // If the folder info reports that the associated folder is open, then verify that
+        // it is actually opened. There have been a few instances where this gets out of sync.
+        if (info.opened && openFolder == null) {
+            Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
+                    + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
+            info.opened = false;
+        }
+
+        if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
+            // Close any open folder
+            closeFolder();
+            // Open the requested folder
+            openFolder(folderIcon);
+        } else {
+            // Find the open folder...
+            int folderScreen;
+            if (openFolder != null) {
+                folderScreen = mWorkspace.getPageForView(openFolder);
+                // .. and close it
+                closeFolder(openFolder);
+                if (folderScreen != mWorkspace.getCurrentPage()) {
+                    // Close any folder open on the current screen
+                    closeFolder();
+                    // Pull the folder onto this screen
+                    openFolder(folderIcon);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
+     * in the DragLayer in the exact absolute location of the original FolderIcon.
+     */
+    private void copyFolderIconToImage(FolderIcon fi) {
+        final int width = fi.getMeasuredWidth();
+        final int height = fi.getMeasuredHeight();
+
+        // Lazy load ImageView, Bitmap and Canvas
+        if (mFolderIconImageView == null) {
+            mFolderIconImageView = new ImageView(this);
+        }
+        if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
+                mFolderIconBitmap.getHeight() != height) {
+            mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            mFolderIconCanvas = new Canvas(mFolderIconBitmap);
+        }
+
+        DragLayer.LayoutParams lp;
+        if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
+            lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
+        } else {
+            lp = new DragLayer.LayoutParams(width, height);
+        }
+
+        // The layout from which the folder is being opened may be scaled, adjust the starting
+        // view size by this scale factor.
+        float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
+        lp.customPosition = true;
+        lp.x = mRectForFolderAnimation.left;
+        lp.y = mRectForFolderAnimation.top;
+        lp.width = (int) (scale * width);
+        lp.height = (int) (scale * height);
+
+        mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        fi.draw(mFolderIconCanvas);
+        mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
+        if (fi.getFolder() != null) {
+            mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
+            mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
+        }
+        // Just in case this image view is still in the drag layer from a previous animation,
+        // we remove it and re-add it.
+        if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
+            mDragLayer.removeView(mFolderIconImageView);
+        }
+        mDragLayer.addView(mFolderIconImageView, lp);
+        if (fi.getFolder() != null) {
+            fi.getFolder().bringToFront();
+        }
+    }
+
+    private void growAndFadeOutFolderIcon(FolderIcon fi) {
+        if (fi == null) return;
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
+
+        FolderInfo info = (FolderInfo) fi.getTag();
+        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            CellLayout cl = (CellLayout) fi.getParent().getParent();
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
+            cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+        }
+
+        // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
+        copyFolderIconToImage(fi);
+        fi.setVisibility(View.INVISIBLE);
+
+        ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
+                scaleX, scaleY);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+        oa.start();
+    }
+
+    private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
+        if (fi == null) return;
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+
+        final CellLayout cl = (CellLayout) fi.getParent().getParent();
+
+        // We remove and re-draw the FolderIcon in-case it has changed
+        mDragLayer.removeView(mFolderIconImageView);
+        copyFolderIconToImage(fi);
+        ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
+                scaleX, scaleY);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (cl != null) {
+                    cl.clearFolderLeaveBehind();
+                    // Remove the ImageView copy of the FolderIcon and make the original visible.
+                    mDragLayer.removeView(mFolderIconImageView);
+                    fi.setVisibility(View.VISIBLE);
+                }
+            }
+        });
+        oa.start();
+    }
+
+    /**
+     * Opens the user folder described by the specified tag. The opening of the folder
+     * is animated relative to the specified View. If the View is null, no animation
+     * is played.
+     *
+     * @param folderInfo The FolderInfo describing the folder to open.
+     */
+    public void openFolder(FolderIcon folderIcon) {
+        Folder folder = folderIcon.getFolder();
+        FolderInfo info = folder.mInfo;
+
+        info.opened = true;
+
+        // Just verify that the folder hasn't already been added to the DragLayer.
+        // There was a one-off crash where the folder had a parent already.
+        if (folder.getParent() == null) {
+            mDragLayer.addView(folder);
+            mDragController.addDropTarget((DropTarget) folder);
+        } else {
+            Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
+                    folder.getParent() + ").");
+        }
+        folder.animateOpen();
+        growAndFadeOutFolderIcon(folderIcon);
+
+        // Notify the accessibility manager that this folder "window" has appeared and occluded
+        // the workspace items
+        folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+    }
+
+    public void closeFolder() {
+        Folder folder = mWorkspace.getOpenFolder();
+        if (folder != null) {
+            if (folder.isEditingName()) {
+                folder.dismissEditingName();
+            }
+            closeFolder(folder);
+
+            // Dismiss the folder cling
+            dismissFolderCling(null);
+        }
+    }
+
+    void closeFolder(Folder folder) {
+        folder.getInfo().opened = false;
+
+        ViewGroup parent = (ViewGroup) folder.getParent().getParent();
+        if (parent != null) {
+            FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
+            shrinkAndFadeInFolderIcon(fi);
+        }
+        folder.animateClosed();
+
+        // Notify the accessibility manager that this folder "window" has disappeard and no
+        // longer occludeds the workspace items
+        getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    public boolean onLongClick(View v) {
+        if (!isDraggingEnabled()) return false;
+        if (isWorkspaceLocked()) return false;
+        if (mState != State.WORKSPACE) return false;
+
+        if (v instanceof Workspace) {
+            if (!mWorkspace.isInOverviewMode()) {
+                if (mWorkspace.enterOverviewMode()) {
+                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+
+        if (!(v instanceof CellLayout)) {
+            v = (View) v.getParent().getParent();
+        }
+
+        resetAddInfo();
+        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
+        // This happens when long clicking an item with the dpad/trackball
+        if (longClickCellInfo == null) {
+            return true;
+        }
+
+        // The hotseat touch handling does not go through Workspace, and we always allow long press
+        // on hotseat items.
+        final View itemUnderLongClick = longClickCellInfo.cell;
+        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
+        if (allowLongPress && !mDragController.isDragging()) {
+            if (itemUnderLongClick == null) {
+                // User long pressed on empty space
+                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                // Disabling reordering until we sort out some issues.
+                if (mWorkspace.isInOverviewMode()) {
+                    mWorkspace.startReordering(v);
+                } else {
+                    mWorkspace.enterOverviewMode();
+                }
+            } else {
+                if (!(itemUnderLongClick instanceof Folder)) {
+                    // User long pressed on an item
+                    mWorkspace.startDrag(longClickCellInfo);
+                }
+            }
+        }
+        return true;
+    }
+
+    boolean isHotseatLayout(View layout) {
+        return mHotseat != null && layout != null &&
+                (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
+    }
+    Hotseat getHotseat() {
+        return mHotseat;
+    }
+    View getOverviewPanel() {
+        return mOverviewPanel;
+    }
+    SearchDropTargetBar getSearchBar() {
+        return mSearchDropTargetBar;
+    }
+
+    /**
+     * Returns the CellLayout of the specified container at the specified screen.
+     */
+    CellLayout getCellLayout(long container, long screenId) {
+        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            if (mHotseat != null) {
+                return mHotseat.getLayout();
+            } else {
+                return null;
+            }
+        } else {
+            return (CellLayout) mWorkspace.getScreenWithId(screenId);
+        }
+    }
+
+    Workspace getWorkspace() {
+        return mWorkspace;
+    }
+
+    public boolean isAllAppsVisible() {
+        return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
+    }
+
+    /**
+     * Helper method for the cameraZoomIn/cameraZoomOut animations
+     * @param view The view being animated
+     * @param scaleFactor The scale factor used for the zoom
+     */
+    private void setPivotsForZoom(View view, float scaleFactor) {
+        view.setPivotX(view.getWidth() / 2.0f);
+        view.setPivotY(view.getHeight() / 2.0f);
+    }
+
+    private void setWorkspaceBackground(boolean workspace) {
+        mLauncherView.setBackground(workspace ?
+                mWorkspaceBackgroundDrawable : null);
+    }
+
+    void updateWallpaperVisibility(boolean visible) {
+        int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
+        int curflags = getWindow().getAttributes().flags
+                & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        if (wpflags != curflags) {
+            getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
+        }
+        setWorkspaceBackground(visible);
+    }
+
+    private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
+        if (v instanceof LauncherTransitionable) {
+            ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
+        }
+    }
+
+    private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
+        if (v instanceof LauncherTransitionable) {
+            ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
+        }
+
+        // Update the workspace transition step as well
+        dispatchOnLauncherTransitionStep(v, 0f);
+    }
+
+    private void dispatchOnLauncherTransitionStep(View v, float t) {
+        if (v instanceof LauncherTransitionable) {
+            ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
+        }
+    }
+
+    private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
+        if (v instanceof LauncherTransitionable) {
+            ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
+        }
+
+        // Update the workspace transition step as well
+        dispatchOnLauncherTransitionStep(v, 1f);
+    }
+
+    /**
+     * Things to test when changing the following seven functions.
+     *   - Home from workspace
+     *          - from center screen
+     *          - from other screens
+     *   - Home from all apps
+     *          - from center screen
+     *          - from other screens
+     *   - Back from all apps
+     *          - from center screen
+     *          - from other screens
+     *   - Launch app from workspace and quit
+     *          - with back
+     *          - with home
+     *   - Launch app from all apps and quit
+     *          - with back
+     *          - with home
+     *   - Go to a screen that's not the default, then all
+     *     apps, and launch and app, and go back
+     *          - with back
+     *          -with home
+     *   - On workspace, long press power and go back
+     *          - with back
+     *          - with home
+     *   - On all apps, long press power and go back
+     *          - with back
+     *          - with home
+     *   - On workspace, power off
+     *   - On all apps, power off
+     *   - Launch an app and turn off the screen while in that app
+     *          - Go back with home key
+     *          - Go back with back key  TODO: make this not go to workspace
+     *          - From all apps
+     *          - From workspace
+     *   - Enter and exit car mode (becuase it causes an extra configuration changed)
+     *          - From all apps
+     *          - From the center workspace
+     *          - From another workspace
+     */
+
+    /**
+     * Zoom the camera out from the workspace to reveal 'toView'.
+     * Assumes that the view to show is anchored at either the very top or very bottom
+     * of the screen.
+     */
+    private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
+        AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
+        showAppsCustomizeHelper(animated, springLoaded, contentType);
+    }
+    private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
+                                         final AppsCustomizePagedView.ContentType contentType) {
+        if (mStateAnimation != null) {
+            mStateAnimation.setDuration(0);
+            mStateAnimation.cancel();
+            mStateAnimation = null;
+        }
+        final Resources res = getResources();
+
+        final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
+        final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
+        final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+        final View fromView = mWorkspace;
+        final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
+        final int startDelay =
+                res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
+
+        setPivotsForZoom(toView, scale);
+
+        // Shrink workspaces away if going to AppsCustomize from workspace
+        Animator workspaceAnim =
+                mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            // Set the content type for the all apps space
+            mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
+        }
+
+        if (animated) {
+            toView.setScaleX(scale);
+            toView.setScaleY(scale);
+            final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
+            scaleAnim.
+                scaleX(1f).scaleY(1f).
+                setDuration(duration).
+                setInterpolator(new Workspace.ZoomOutInterpolator());
+
+            toView.setVisibility(View.VISIBLE);
+            toView.setAlpha(0f);
+            final ObjectAnimator alphaAnim = LauncherAnimUtils
+                .ofFloat(toView, "alpha", 0f, 1f)
+                .setDuration(fadeDuration);
+            alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
+            alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    if (animation == null) {
+                        throw new RuntimeException("animation is null");
+                    }
+                    float t = (Float) animation.getAnimatedValue();
+                    dispatchOnLauncherTransitionStep(fromView, t);
+                    dispatchOnLauncherTransitionStep(toView, t);
+                }
+            });
+
+            // toView should appear right at the end of the workspace shrink
+            // animation
+            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+            mStateAnimation.play(scaleAnim).after(startDelay);
+            mStateAnimation.play(alphaAnim).after(startDelay);
+
+            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    // Prepare the position
+                    toView.setTranslationX(0.0f);
+                    toView.setTranslationY(0.0f);
+                    toView.setVisibility(View.VISIBLE);
+                    toView.bringToFront();
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
+                    dispatchOnLauncherTransitionEnd(toView, animated, false);
+
+                    // Hide the search bar
+                    if (mSearchDropTargetBar != null) {
+                        mSearchDropTargetBar.hideSearchBar(false);
+                    }
+                }
+            });
+
+            if (workspaceAnim != null) {
+                mStateAnimation.play(workspaceAnim);
+            }
+
+            boolean delayAnim = false;
+
+            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+            dispatchOnLauncherTransitionPrepare(toView, animated, false);
+
+            // If any of the objects being animated haven't been measured/laid out
+            // yet, delay the animation until we get a layout pass
+            if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
+                    (mWorkspace.getMeasuredWidth() == 0) ||
+                    (toView.getMeasuredWidth() == 0)) {
+                delayAnim = true;
+            }
+
+            final AnimatorSet stateAnimation = mStateAnimation;
+            final Runnable startAnimRunnable = new Runnable() {
+                public void run() {
+                    // Check that mStateAnimation hasn't changed while
+                    // we waited for a layout/draw pass
+                    if (mStateAnimation != stateAnimation)
+                        return;
+                    setPivotsForZoom(toView, scale);
+                    dispatchOnLauncherTransitionStart(fromView, animated, false);
+                    dispatchOnLauncherTransitionStart(toView, animated, false);
+                    LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
+                }
+            };
+            if (delayAnim) {
+                final ViewTreeObserver observer = toView.getViewTreeObserver();
+                observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                        public void onGlobalLayout() {
+                            startAnimRunnable.run();
+                            toView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        }
+                    });
+            } else {
+                startAnimRunnable.run();
+            }
+        } else {
+            toView.setTranslationX(0.0f);
+            toView.setTranslationY(0.0f);
+            toView.setScaleX(1.0f);
+            toView.setScaleY(1.0f);
+            toView.setVisibility(View.VISIBLE);
+            toView.bringToFront();
+
+            if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
+                // Hide the search bar
+                if (mSearchDropTargetBar != null) {
+                    mSearchDropTargetBar.hideSearchBar(false);
+                }
+            }
+            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+            dispatchOnLauncherTransitionStart(fromView, animated, false);
+            dispatchOnLauncherTransitionEnd(fromView, animated, false);
+            dispatchOnLauncherTransitionPrepare(toView, animated, false);
+            dispatchOnLauncherTransitionStart(toView, animated, false);
+            dispatchOnLauncherTransitionEnd(toView, animated, false);
+        }
+    }
+
+    /**
+     * Zoom the camera back into the workspace, hiding 'fromView'.
+     * This is the opposite of showAppsCustomizeHelper.
+     * @param animated If true, the transition will be animated.
+     */
+    private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated,
+            final boolean springLoaded, final Runnable onCompleteRunnable) {
+
+        if (mStateAnimation != null) {
+            mStateAnimation.setDuration(0);
+            mStateAnimation.cancel();
+            mStateAnimation = null;
+        }
+        Resources res = getResources();
+
+        final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
+        final int fadeOutDuration =
+                res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
+        final float scaleFactor = (float)
+                res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+        final View fromView = mAppsCustomizeTabHost;
+        final View toView = mWorkspace;
+        Animator workspaceAnim = null;
+        if (toState == Workspace.State.NORMAL) {
+            int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
+            workspaceAnim = mWorkspace.getChangeStateAnimation(
+                    toState, animated, stagger, -1);
+        } else if (toState == Workspace.State.SPRING_LOADED ||
+                toState == Workspace.State.OVERVIEW) {
+            workspaceAnim = mWorkspace.getChangeStateAnimation(
+                    toState, animated);
+        }
+
+        setPivotsForZoom(fromView, scaleFactor);
+        showHotseat(animated);
+        if (animated) {
+            final LauncherViewPropertyAnimator scaleAnim =
+                    new LauncherViewPropertyAnimator(fromView);
+            scaleAnim.
+                scaleX(scaleFactor).scaleY(scaleFactor).
+                setDuration(duration).
+                setInterpolator(new Workspace.ZoomInInterpolator());
+
+            final ObjectAnimator alphaAnim = LauncherAnimUtils
+                .ofFloat(fromView, "alpha", 1f, 0f)
+                .setDuration(fadeOutDuration);
+            alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+            alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float t = 1f - (Float) animation.getAnimatedValue();
+                    dispatchOnLauncherTransitionStep(fromView, t);
+                    dispatchOnLauncherTransitionStep(toView, t);
+                }
+            });
+
+            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+
+            dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+            dispatchOnLauncherTransitionPrepare(toView, animated, true);
+            mAppsCustomizeContent.pauseScrolling();
+
+            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    fromView.setVisibility(View.GONE);
+                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
+                    dispatchOnLauncherTransitionEnd(toView, animated, true);
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+                    mAppsCustomizeContent.updateCurrentPageScroll();
+                    mAppsCustomizeContent.resumeScrolling();
+                }
+            });
+
+            mStateAnimation.playTogether(scaleAnim, alphaAnim);
+            if (workspaceAnim != null) {
+                mStateAnimation.play(workspaceAnim);
+            }
+            dispatchOnLauncherTransitionStart(fromView, animated, true);
+            dispatchOnLauncherTransitionStart(toView, animated, true);
+            LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
+        } else {
+            fromView.setVisibility(View.GONE);
+            dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+            dispatchOnLauncherTransitionStart(fromView, animated, true);
+            dispatchOnLauncherTransitionEnd(fromView, animated, true);
+            dispatchOnLauncherTransitionPrepare(toView, animated, true);
+            dispatchOnLauncherTransitionStart(toView, animated, true);
+            dispatchOnLauncherTransitionEnd(toView, animated, true);
+        }
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
+            mAppsCustomizeTabHost.onTrimMemory();
+        }
+    }
+
+    protected void showWorkspace(boolean animated) {
+        showWorkspace(animated, null);
+    }
+
+    protected void showWorkspace() {
+        showWorkspace(true);
+    }
+
+    void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+        if (mWorkspace.isInOverviewMode()) {
+            mWorkspace.exitOverviewMode(animated);
+        }
+        if (mState != State.WORKSPACE) {
+            boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
+            mWorkspace.setVisibility(View.VISIBLE);
+            hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
+
+            // Show the search bar (only animate if we were showing the drop target bar in spring
+            // loaded mode)
+            if (mSearchDropTargetBar != null) {
+                mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
+            }
+
+            // Set focus to the AppsCustomize button
+            if (mAllAppsButton != null) {
+                mAllAppsButton.requestFocus();
+            }
+        }
+
+        // Change the state *after* we've called all the transition code
+        mState = State.WORKSPACE;
+
+        // Resume the auto-advance of widgets
+        mUserPresent = true;
+        updateRunning();
+
+        // Send an accessibility event to announce the context change
+        getWindow().getDecorView()
+                .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+
+        onWorkspaceShown(animated);
+    }
+
+    void showOverviewMode(boolean animated) {
+        mWorkspace.setVisibility(View.VISIBLE);
+        hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
+        mState = State.WORKSPACE;
+        onWorkspaceShown(animated);
+    }
+
+    public void onWorkspaceShown(boolean animated) {
+    }
+
+    void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
+                     boolean resetPageToZero) {
+        if (mState != State.WORKSPACE) return;
+
+        if (resetPageToZero) {
+            mAppsCustomizeTabHost.reset();
+        }
+        showAppsCustomizeHelper(animated, false, contentType);
+        mAppsCustomizeTabHost.requestFocus();
+
+        // Change the state *after* we've called all the transition code
+        mState = State.APPS_CUSTOMIZE;
+
+        // Pause the auto-advance of widgets until we are out of AllApps
+        mUserPresent = false;
+        updateRunning();
+        closeFolder();
+
+        // Send an accessibility event to announce the context change
+        getWindow().getDecorView()
+                .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void enterSpringLoadedDragMode() {
+        if (isAllAppsVisible()) {
+            hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
+            mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
+        }
+    }
+
+    void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+            final Runnable onCompleteRunnable) {
+        if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (successfulDrop) {
+                    // Before we show workspace, hide all apps again because
+                    // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
+                    // clean up our state transition functions
+                    mAppsCustomizeTabHost.setVisibility(View.GONE);
+                    showWorkspace(true, onCompleteRunnable);
+                } else {
+                    exitSpringLoadedDragMode();
+                }
+            }
+        }, (extendedDelay ?
+                EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
+                EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
+    }
+
+    void exitSpringLoadedDragMode() {
+        if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
+            final boolean animated = true;
+            final boolean springLoaded = true;
+            showAppsCustomizeHelper(animated, springLoaded);
+            mState = State.APPS_CUSTOMIZE;
+        }
+        // Otherwise, we are not in spring loaded mode, so don't do anything.
+    }
+
+    void lockAllApps() {
+        // TODO
+    }
+
+    void unlockAllApps() {
+        // TODO
+    }
+
+    /**
+     * Shows the hotseat area.
+     */
+    void showHotseat(boolean animated) {
+        if (!LauncherAppState.getInstance().isScreenLarge()) {
+            if (animated) {
+                if (mHotseat.getAlpha() != 1f) {
+                    int duration = 0;
+                    if (mSearchDropTargetBar != null) {
+                        duration = mSearchDropTargetBar.getTransitionInDuration();
+                    }
+                    mHotseat.animate().alpha(1f).setDuration(duration);
+                }
+            } else {
+                mHotseat.setAlpha(1f);
+            }
+        }
+    }
+
+    /**
+     * Hides the hotseat area.
+     */
+    void hideHotseat(boolean animated) {
+        if (!LauncherAppState.getInstance().isScreenLarge()) {
+            if (animated) {
+                if (mHotseat.getAlpha() != 0f) {
+                    int duration = 0;
+                    if (mSearchDropTargetBar != null) {
+                        duration = mSearchDropTargetBar.getTransitionOutDuration();
+                    }
+                    mHotseat.animate().alpha(0f).setDuration(duration);
+                }
+            } else {
+                mHotseat.setAlpha(0f);
+            }
+        }
+    }
+
+    /**
+     * Add an item from all apps or customize onto the given workspace screen.
+     * If layout is null, add to the current screen.
+     */
+    void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
+        if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
+            showOutOfSpaceMessage(isHotseatLayout(layout));
+        }
+    }
+
+    /** Maps the current orientation to an index for referencing orientation correct global icons */
+    private int getCurrentOrientationIndexForGlobalIcons() {
+        // default - 0, landscape - 1
+        switch (getResources().getConfiguration().orientation) {
+        case Configuration.ORIENTATION_LANDSCAPE:
+            return 1;
+        default:
+            return 0;
+        }
+    }
+
+    private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
+        try {
+            PackageManager packageManager = getPackageManager();
+            // Look for the toolbar icon specified in the activity meta-data
+            Bundle metaData = packageManager.getActivityInfo(
+                    activityName, PackageManager.GET_META_DATA).metaData;
+            if (metaData != null) {
+                int iconResId = metaData.getInt(resourceName);
+                if (iconResId != 0) {
+                    Resources res = packageManager.getResourcesForActivity(activityName);
+                    return res.getDrawable(iconResId);
+                }
+            }
+        } catch (NameNotFoundException e) {
+            // This can happen if the activity defines an invalid drawable
+            Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
+                    " not found", e);
+        } catch (Resources.NotFoundException nfe) {
+            // This can happen if the activity defines an invalid drawable
+            Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
+                    nfe);
+        }
+        return null;
+    }
+
+    // if successful in getting icon, return it; otherwise, set button to use default drawable
+    private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
+            int buttonId, ComponentName activityName, int fallbackDrawableId,
+            String toolbarResourceName) {
+        Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
+        Resources r = getResources();
+        int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
+        int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
+
+        TextView button = (TextView) findViewById(buttonId);
+        // If we were unable to find the icon via the meta-data, use a generic one
+        if (toolbarIcon == null) {
+            toolbarIcon = r.getDrawable(fallbackDrawableId);
+            toolbarIcon.setBounds(0, 0, w, h);
+            if (button != null) {
+                button.setCompoundDrawables(toolbarIcon, null, null, null);
+            }
+            return null;
+        } else {
+            toolbarIcon.setBounds(0, 0, w, h);
+            if (button != null) {
+                button.setCompoundDrawables(toolbarIcon, null, null, null);
+            }
+            return toolbarIcon.getConstantState();
+        }
+    }
+
+    // if successful in getting icon, return it; otherwise, set button to use default drawable
+    private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
+            int buttonId, ComponentName activityName, int fallbackDrawableId,
+            String toolbarResourceName) {
+        ImageView button = (ImageView) findViewById(buttonId);
+        Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
+
+        if (button != null) {
+            // If we were unable to find the icon via the meta-data, use a
+            // generic one
+            if (toolbarIcon == null) {
+                button.setImageResource(fallbackDrawableId);
+            } else {
+                button.setImageDrawable(toolbarIcon);
+            }
+        }
+
+        return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
+
+    }
+
+    private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
+        TextView button = (TextView) findViewById(buttonId);
+        button.setCompoundDrawables(d, null, null, null);
+    }
+
+    private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
+        ImageView button = (ImageView) findViewById(buttonId);
+        button.setImageDrawable(d.newDrawable(getResources()));
+    }
+
+    private void invalidatePressedFocusedStates(View container, View button) {
+        if (container instanceof HolographicLinearLayout) {
+            HolographicLinearLayout layout = (HolographicLinearLayout) container;
+            layout.invalidatePressedFocusedStates();
+        } else if (button instanceof HolographicImageView) {
+            HolographicImageView view = (HolographicImageView) button;
+            view.invalidatePressedFocusedStates();
+        }
+    }
+
+    public View getQsbBar() {
+        if (mQsbBar == null) {
+            mQsbBar = mInflater.inflate(R.layout.search_bar, mSearchDropTargetBar, false);
+            mSearchDropTargetBar.addView(mQsbBar);
+        }
+        return mQsbBar;
+    }
+
+    protected boolean updateGlobalSearchIcon() {
+        final View searchButtonContainer = findViewById(R.id.search_button_container);
+        final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
+        final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+        final View voiceButton = findViewById(R.id.voice_button);
+
+        final SearchManager searchManager =
+                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        ComponentName activityName = searchManager.getGlobalSearchActivity();
+        if (activityName != null) {
+            int coi = getCurrentOrientationIndexForGlobalIcons();
+            sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+                    R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
+                    TOOLBAR_SEARCH_ICON_METADATA_NAME);
+            if (sGlobalSearchIcon[coi] == null) {
+                sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+                        R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
+                        TOOLBAR_ICON_METADATA_NAME);
+            }
+
+            if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
+            searchButton.setVisibility(View.VISIBLE);
+            invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+            return true;
+        } else {
+            // We disable both search and voice search when there is no global search provider
+            if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
+            if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+            if (searchButton != null) searchButton.setVisibility(View.GONE);
+            if (voiceButton != null) voiceButton.setVisibility(View.GONE);
+            updateVoiceButtonProxyVisible(false);
+            return false;
+        }
+    }
+
+    protected void updateGlobalSearchIcon(Drawable.ConstantState d) {
+        final View searchButtonContainer = findViewById(R.id.search_button_container);
+        final View searchButton = (ImageView) findViewById(R.id.search_button);
+        updateButtonWithDrawable(R.id.search_button, d);
+        invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+    }
+
+    protected boolean updateVoiceSearchIcon(boolean searchVisible) {
+        final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+        final View voiceButton = findViewById(R.id.voice_button);
+
+        // We only show/update the voice search icon if the search icon is enabled as well
+        final SearchManager searchManager =
+                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
+
+        ComponentName activityName = null;
+        if (globalSearchActivity != null) {
+            // Check if the global search activity handles voice search
+            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+            intent.setPackage(globalSearchActivity.getPackageName());
+            activityName = intent.resolveActivity(getPackageManager());
+        }
+
+        if (activityName == null) {
+            // Fallback: check if an activity other than the global search activity
+            // resolves this
+            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+            activityName = intent.resolveActivity(getPackageManager());
+        }
+        if (searchVisible && activityName != null) {
+            int coi = getCurrentOrientationIndexForGlobalIcons();
+            sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+                    R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
+                    TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
+            if (sVoiceSearchIcon[coi] == null) {
+                sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+                        R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
+                        TOOLBAR_ICON_METADATA_NAME);
+            }
+            if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
+            voiceButton.setVisibility(View.VISIBLE);
+            updateVoiceButtonProxyVisible(false);
+            invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+            return true;
+        } else {
+            if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+            if (voiceButton != null) voiceButton.setVisibility(View.GONE);
+            updateVoiceButtonProxyVisible(false);
+            return false;
+        }
+    }
+
+    protected void updateVoiceSearchIcon(Drawable.ConstantState d) {
+        final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+        final View voiceButton = findViewById(R.id.voice_button);
+        updateButtonWithDrawable(R.id.voice_button, d);
+        invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+    }
+
+    public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) {
+        final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
+        if (voiceButtonProxy != null) {
+            boolean visible = !forceDisableVoiceButtonProxy &&
+                    mWorkspace.shouldVoiceButtonProxyBeVisible();
+            voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE);
+            voiceButtonProxy.bringToFront();
+        }
+    }
+
+    /**
+     * This is an overrid eot disable the voice button proxy.  If disabled is true, then the voice button proxy
+     * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns.
+     */
+    public void disableVoiceButtonProxy(boolean disabled) {
+        updateVoiceButtonProxyVisible(disabled);
+    }
+    /**
+     * Sets the app market icon
+     */
+    private void updateAppMarketIcon() {
+        if (!DISABLE_MARKET_BUTTON) {
+            final View marketButton = findViewById(R.id.market_button);
+            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
+            // Find the app market activity by resolving an intent.
+            // (If multiple app markets are installed, it will return the ResolverActivity.)
+            ComponentName activityName = intent.resolveActivity(getPackageManager());
+            if (activityName != null) {
+                int coi = getCurrentOrientationIndexForGlobalIcons();
+                mAppMarketIntent = intent;
+                sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
+                        R.id.market_button, activityName, R.drawable.ic_launcher_market_holo,
+                        TOOLBAR_ICON_METADATA_NAME);
+                marketButton.setVisibility(View.VISIBLE);
+            } else {
+                // We should hide and disable the view so that we don't try and restore the visibility
+                // of it when we swap between drag & normal states from IconDropTarget subclasses.
+                marketButton.setVisibility(View.GONE);
+                marketButton.setEnabled(false);
+            }
+        }
+    }
+
+    private void updateAppMarketIcon(Drawable.ConstantState d) {
+        if (!DISABLE_MARKET_BUTTON) {
+            // Ensure that the new drawable we are creating has the approprate toolbar icon bounds
+            Resources r = getResources();
+            Drawable marketIconDrawable = d.newDrawable(r);
+            int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
+            int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
+            marketIconDrawable.setBounds(0, 0, w, h);
+
+            updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        final boolean result = super.dispatchPopulateAccessibilityEvent(event);
+        final List<CharSequence> text = event.getText();
+        text.clear();
+        // Populate event with a fake title based on the current state.
+        if (mState == State.APPS_CUSTOMIZE) {
+            text.add(mAppsCustomizeTabHost.getCurrentTabView().getContentDescription());
+        } else {
+            text.add(getString(R.string.all_apps_home_button_label));
+        }
+        return result;
+    }
+
+    /**
+     * Receives notifications when system dialogs are to be closed.
+     */
+    private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            closeSystemDialogs();
+        }
+    }
+
+    /**
+     * Receives notifications whenever the appwidgets are reset.
+     */
+    private class AppWidgetResetObserver extends ContentObserver {
+        public AppWidgetResetObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onAppWidgetReset();
+        }
+    }
+
+    /**
+     * If the activity is currently paused, signal that we need to run the passed Runnable
+     * in onResume.
+     *
+     * This needs to be called from incoming places where resources might have been loaded
+     * while we are paused.  That is becaues the Configuration might be wrong
+     * when we're not running, and if it comes back to what it was when we
+     * were paused, we are not restarted.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     *
+     * @return true if we are currently paused.  The caller might be able to
+     * skip some work in that case since we will come back again.
+     */
+    private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
+        if (mPaused) {
+            Log.i(TAG, "Deferring update until onResume");
+            if (deletePreviousRunnables) {
+                while (mBindOnResumeCallbacks.remove(run)) {
+                }
+            }
+            mBindOnResumeCallbacks.add(run);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean waitUntilResume(Runnable run) {
+        return waitUntilResume(run, false);
+    }
+
+    public void addOnResumeCallback(Runnable run) {
+        mOnResumeCallbacks.add(run);
+    }
+
+    /**
+     * If the activity is currently paused, signal that we need to re-run the loader
+     * in onResume.
+     *
+     * This needs to be called from incoming places where resources might have been loaded
+     * while we are paused.  That is becaues the Configuration might be wrong
+     * when we're not running, and if it comes back to what it was when we
+     * were paused, we are not restarted.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     *
+     * @return true if we are currently paused.  The caller might be able to
+     * skip some work in that case since we will come back again.
+     */
+    public boolean setLoadOnResume() {
+        if (mPaused) {
+            Log.i(TAG, "setLoadOnResume");
+            mOnResumeNeedsLoad = true;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public int getCurrentWorkspaceScreen() {
+        if (mWorkspace != null) {
+            return mWorkspace.getCurrentPage();
+        } else {
+            return SCREEN_COUNT / 2;
+        }
+    }
+
+    /**
+     * Refreshes the shortcuts shown on the workspace.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void startBinding() {
+        // If we're starting binding all over again, clear any bind calls we'd postponed in
+        // the past (see waitUntilResume) -- we don't need them since we're starting binding
+        // from scratch again
+        mBindOnResumeCallbacks.clear();
+
+        // Clear the workspace because it's going to be rebound
+        mWorkspace.clearDropTargets();
+        mWorkspace.removeAllWorkspaceScreens();
+
+        mWidgetsToAdvance.clear();
+        if (mHotseat != null) {
+            mHotseat.resetLayout();
+        }
+    }
+
+    @Override
+    public void bindScreens(ArrayList<Long> orderedScreenIds) {
+        bindAddScreens(orderedScreenIds);
+
+        // If there are no screens, we need to have an empty screen
+        if (orderedScreenIds.size() == 0) {
+            mWorkspace.addExtraEmptyScreen();
+        }
+
+        // Create the custom content page (this call updates mDefaultScreen which calls
+        // setCurrentPage() so ensure that all pages are added before calling this).
+        // The actual content of the custom page will be added during onFinishBindingItems().
+        if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
+            mWorkspace.createCustomContentPage();
+        }
+    }
+
+    @Override
+    public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
+        int count = orderedScreenIds.size();
+        for (int i = 0; i < count; i++) {
+            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
+        }
+    }
+
+    private boolean shouldShowWeightWatcher() {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
+
+        return show;
+    }
+
+    private void toggleShowWeightWatcher() {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
+
+        show = !show;
+
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
+        editor.commit();
+
+        if (mWeightWatcher != null) {
+            mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public void bindAppsAdded(final ArrayList<Long> newScreens,
+                              final ArrayList<ItemInfo> addNotAnimated,
+                              final ArrayList<ItemInfo> addAnimated,
+                              final ArrayList<AppInfo> addedApps) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        // Add the new screens
+        bindAddScreens(newScreens);
+
+        // We add the items without animation on non-visible pages, and with
+        // animations on the new page (which we will try and snap to).
+        if (!addNotAnimated.isEmpty()) {
+            bindItems(addNotAnimated, 0,
+                    addNotAnimated.size(), false);
+        }
+        if (!addAnimated.isEmpty()) {
+            bindItems(addAnimated, 0,
+                    addAnimated.size(), true);
+        }
+
+        // Remove the extra empty screen
+        mWorkspace.removeExtraEmptyScreen();
+
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                addedApps != null && mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.addApps(addedApps);
+        }
+    }
+
+    /**
+     * Bind the items start-end from the list.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+                          final boolean forceAnimateIcons) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindItems(shortcuts, start, end, forceAnimateIcons);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        // Get the list of added shortcuts and intersect them with the set of shortcuts here
+        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        final Collection<Animator> bounceAnims = new ArrayList<Animator>();
+        final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
+        Workspace workspace = mWorkspace;
+        long newShortcutsScreenId = -1;
+        for (int i = start; i < end; i++) {
+            final ItemInfo item = shortcuts.get(i);
+
+            // Short circuit if we are loading dock items for a configuration which has no dock
+            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+                    mHotseat == null) {
+                continue;
+            }
+
+            switch (item.itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    View shortcut = createShortcut(info);
+
+                    /*
+                     * TODO: FIX collision case
+                     */
+                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                        CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
+                        if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
+                            throw new RuntimeException("OCCUPIED");
+                        }
+                    }
+
+                    workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
+                            item.cellY, 1, 1);
+                    if (animateIcons) {
+                        // Animate all the applications up now
+                        shortcut.setAlpha(0f);
+                        shortcut.setScaleX(0f);
+                        shortcut.setScaleY(0f);
+                        bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
+                        newShortcutsScreenId = item.screenId;
+                    }
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
+                            (FolderInfo) item, mIconCache);
+                    workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
+                            item.cellY, 1, 1);
+                    break;
+                default:
+                    throw new RuntimeException("Invalid Item Type");
+            }
+        }
+
+        if (animateIcons) {
+            // Animate to the correct page
+            if (newShortcutsScreenId > -1) {
+                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+                final Runnable startBounceAnimRunnable = new Runnable() {
+                    public void run() {
+                        anim.playTogether(bounceAnims);
+                        anim.start();
+                    }
+                };
+                if (newShortcutsScreenId != currentScreenId) {
+                    // We post the animation slightly delayed to prevent slowdowns
+                    // when we are loading right after we return to launcher.
+                    mWorkspace.postDelayed(new Runnable() {
+                        public void run() {
+                            mWorkspace.snapToPage(newScreenIndex);
+                            mWorkspace.postDelayed(startBounceAnimRunnable,
+                                    NEW_APPS_ANIMATION_DELAY);
+                        }
+                    }, NEW_APPS_PAGE_MOVE_DELAY);
+                } else {
+                    mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
+                }
+            }
+        }
+        workspace.requestLayout();
+    }
+
+    /**
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindFolders(final HashMap<Long, FolderInfo> folders) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindFolders(folders);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+        sFolders.clear();
+        sFolders.putAll(folders);
+    }
+
+    /**
+     * Add the views for a widget to the workspace.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindAppWidget(final LauncherAppWidgetInfo item) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindAppWidget(item);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bindAppWidget: " + item);
+        }
+        final Workspace workspace = mWorkspace;
+
+        final int appWidgetId = item.appWidgetId;
+        final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
+        }
+
+        item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+
+        item.hostView.setTag(item);
+        item.onBindAppWidget(this);
+
+        workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
+                item.cellY, item.spanX, item.spanY, false);
+        addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
+
+        workspace.requestLayout();
+
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+                    + (SystemClock.uptimeMillis()-start) + "ms");
+        }
+    }
+
+    public void onPageBoundSynchronously(int page) {
+        mSynchronouslyBoundPages.add(page);
+    }
+
+    /**
+     * Callback saying that there aren't any more items to bind.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void finishBindingItems(final boolean upgradePath) {
+        Runnable r = new Runnable() {
+            public void run() {
+                finishBindingItems(upgradePath);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+        if (mSavedState != null) {
+            if (!mWorkspace.hasFocus()) {
+                mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
+            }
+            mSavedState = null;
+        }
+
+        mWorkspace.restoreInstanceStateForRemainingPages();
+
+        // If we received the result of any pending adds while the loader was running (e.g. the
+        // widget configuration forced an orientation change), process them now.
+        for (int i = 0; i < sPendingAddList.size(); i++) {
+            completeAdd(sPendingAddList.get(i));
+        }
+        sPendingAddList.clear();
+
+        // Update the market app icon as necessary (the other icons will be managed in response to
+        // package changes in bindSearchablesChanged()
+        if (!DISABLE_MARKET_BUTTON) {
+            updateAppMarketIcon();
+        }
+
+        mWorkspaceLoading = false;
+        if (upgradePath) {
+            mWorkspace.getUniqueComponents(true, null);
+            mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
+        }
+
+        mWorkspace.post(new Runnable() {
+            @Override
+            public void run() {
+                onFinishBindingItems();
+            }
+        });
+    }
+
+    public boolean isAllAppsButtonRank(int rank) {
+        if (mHotseat != null) {
+            return mHotseat.isAllAppsButtonRank(rank);
+        }
+        return false;
+    }
+
+    private boolean canRunNewAppsAnimation() {
+        long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
+        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+    }
+
+    private ValueAnimator createNewAppBounceAnimation(View v, int i) {
+        ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
+                PropertyValuesHolder.ofFloat("alpha", 1f),
+                PropertyValuesHolder.ofFloat("scaleX", 1f),
+                PropertyValuesHolder.ofFloat("scaleY", 1f));
+        bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+        bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+        bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
+        return bounceAnim;
+    }
+
+    @Override
+    public void bindSearchablesChanged() {
+        boolean searchVisible = updateGlobalSearchIcon();
+        boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
+        if (mSearchDropTargetBar != null) {
+            mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+        }
+    }
+
+    /**
+     * Add the icons for all apps.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindAllApplications(final ArrayList<AppInfo> apps) {
+        if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+            if (mIntentsOnWorkspaceFromUpgradePath != null) {
+                if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
+                    getHotseat().addAllAppsFolder(mIconCache, apps,
+                            mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
+                }
+                mIntentsOnWorkspaceFromUpgradePath = null;
+            }
+        } else {
+            if (mAppsCustomizeContent != null) {
+                mAppsCustomizeContent.setApps(apps);
+            }
+        }
+    }
+
+    /**
+     * A package was updated.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindAppsUpdated(apps);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        if (mWorkspace != null) {
+            mWorkspace.updateShortcuts(apps);
+        }
+
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.updateApps(apps);
+        }
+    }
+
+    /**
+     * A package was uninstalled.  We take both the super set of packageNames
+     * in addition to specific applications to remove, the reason being that
+     * this can be called when a package is updated as well.  In that scenario,
+     * we only remove specific components from the workspace, where as
+     * package-removal should clear all items by package name.
+     *
+     * Implementation of the method from LauncherModel.Callbacks.
+     */
+    public void bindComponentsRemoved(final ArrayList<String> packageNames,
+                                      final ArrayList<AppInfo> appInfos,
+                                      final boolean packageRemoved) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindComponentsRemoved(packageNames, appInfos, packageRemoved);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        if (packageRemoved) {
+            mWorkspace.removeItemsByPackageName(packageNames);
+        } else {
+            mWorkspace.removeItemsByApplicationInfo(appInfos);
+        }
+
+        // Notify the drag controller
+        mDragController.onAppsRemoved(appInfos, this);
+
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.removeApps(appInfos);
+        }
+    }
+
+    /**
+     * A number of packages were updated.
+     */
+    private ArrayList<Object> mWidgetsAndShortcuts;
+    private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
+            public void run() {
+                bindPackagesUpdated(mWidgetsAndShortcuts);
+                mWidgetsAndShortcuts = null;
+            }
+        };
+
+    public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
+        if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
+            mWidgetsAndShortcuts = widgetsAndShortcuts;
+            return;
+        }
+
+        // Update the widgets pane
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+                mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
+        }
+    }
+
+    private int mapConfigurationOriActivityInfoOri(int configOri) {
+        final Display d = getWindowManager().getDefaultDisplay();
+        int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
+        switch (d.getRotation()) {
+        case Surface.ROTATION_0:
+        case Surface.ROTATION_180:
+            // We are currently in the same basic orientation as the natural orientation
+            naturalOri = configOri;
+            break;
+        case Surface.ROTATION_90:
+        case Surface.ROTATION_270:
+            // We are currently in the other basic orientation to the natural orientation
+            naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
+                    Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+            break;
+        }
+
+        int[] oriMap = {
+                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+                ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+                ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+        };
+        // Since the map starts at portrait, we need to offset if this device's natural orientation
+        // is landscape.
+        int indexOffset = 0;
+        if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
+            indexOffset = 1;
+        }
+        return oriMap[(d.getRotation() + indexOffset) % 4];
+    }
+
+    public boolean isRotationEnabled() {
+        boolean enableRotation = sForceEnableRotation ||
+                getResources().getBoolean(R.bool.allow_rotation);
+        return enableRotation;
+    }
+    public void lockScreenOrientation() {
+        if (isRotationEnabled()) {
+            setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
+                    .getConfiguration().orientation));
+        }
+    }
+    public void unlockScreenOrientation(boolean immediate) {
+        if (isRotationEnabled()) {
+            if (immediate) {
+                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            } else {
+                mHandler.postDelayed(new Runnable() {
+                    public void run() {
+                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+                    }
+                }, mRestoreScreenOrientationDelay);
+            }
+        }
+    }
+
+    /* Cling related */
+    private boolean isClingsEnabled() {
+        if (DISABLE_CLINGS) {
+            return false;
+        }
+
+        // For now, limit only to phones
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        if (grid.isTablet()) {
+            return false;
+        }
+
+        // disable clings when running in a test harness
+        if(ActivityManager.isRunningInTestHarness()) return false;
+
+        // Disable clings for accessibility when explore by touch is enabled
+        final AccessibilityManager a11yManager = (AccessibilityManager) getSystemService(
+                ACCESSIBILITY_SERVICE);
+        if (a11yManager.isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        // Restricted secondary users (child mode) will potentially have very few apps
+        // seeded when they start up for the first time. Clings won't work well with that
+//        boolean supportsLimitedUsers =
+//                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+//        Account[] accounts = AccountManager.get(this).getAccounts();
+//        if (supportsLimitedUsers && accounts.length == 0) {
+//            UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+//            Bundle restrictions = um.getUserRestrictions();
+//            if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
+//               return false;
+//            }
+//        }
+        return true;
+    }
+
+    private Cling initCling(int clingId, int scrimId, boolean animate,
+                            boolean dimNavBarVisibilty) {
+        Cling cling = (Cling) findViewById(clingId);
+        View scrim = null;
+        if (scrimId > 0) {
+            scrim = findViewById(R.id.cling_scrim);
+        }
+        if (cling != null) {
+            cling.init(this, scrim);
+            cling.show(animate, SHOW_CLING_DURATION);
+
+            if (dimNavBarVisibilty) {
+                cling.setSystemUiVisibility(cling.getSystemUiVisibility() |
+                        View.SYSTEM_UI_FLAG_LOW_PROFILE);
+            }
+        }
+        return cling;
+    }
+
+    private void dismissCling(final Cling cling, final Runnable postAnimationCb,
+                              final String flag, int duration, boolean restoreNavBarVisibilty) {
+        // To catch cases where siblings of top-level views are made invisible, just check whether
+        // the cling is directly set to GONE before dismissing it.
+        if (cling != null && cling.getVisibility() != View.GONE) {
+            final Runnable cleanUpClingCb = new Runnable() {
+                public void run() {
+                    cling.cleanup();
+                    // We should update the shared preferences on a background thread
+                    new Thread("dismissClingThread") {
+                        public void run() {
+                            SharedPreferences.Editor editor = mSharedPrefs.edit();
+                            editor.putBoolean(flag, true);
+                            editor.commit();
+                        }
+                    }.start();
+                    if (postAnimationCb != null) {
+                        postAnimationCb.run();
+                    }
+                }
+            };
+            if (duration <= 0) {
+                cleanUpClingCb.run();
+            } else {
+                cling.hide(duration, cleanUpClingCb);
+            }
+            mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
+
+            if (restoreNavBarVisibilty) {
+                cling.setSystemUiVisibility(cling.getSystemUiVisibility() &
+                        ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
+            }
+        }
+    }
+
+    private void removeCling(int id) {
+        final View cling = findViewById(id);
+        if (cling != null) {
+            final ViewGroup parent = (ViewGroup) cling.getParent();
+            parent.post(new Runnable() {
+                @Override
+                public void run() {
+                    parent.removeView(cling);
+                }
+            });
+            mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
+        }
+    }
+
+    private boolean skipCustomClingIfNoAccounts() {
+        Cling cling = (Cling) findViewById(R.id.workspace_cling);
+        boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
+        if (customCling) {
+            AccountManager am = AccountManager.get(this);
+            if (am == null) return false;
+            Account[] accounts = am.getAccountsByType("com.google");
+            return accounts.length == 0;
+        }
+        return false;
+    }
+
+    public void updateCustomContentHintVisibility() {
+        Cling cling = (Cling) findViewById(R.id.first_run_cling);
+        String ccHintStr = getFirstRunCustomContentHint();
+
+        if (mWorkspace.hasCustomContent()) {
+            // Show the custom content hint if ccHintStr is not empty
+            if (cling != null) {
+                setCustomContentHintVisibility(cling, ccHintStr, true, true);
+            }
+        } else {
+            // Hide the custom content hint
+            if (cling != null) {
+                setCustomContentHintVisibility(cling, ccHintStr, false, true);
+            }
+        }
+    }
+
+    private void setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible,
+                                                boolean animate) {
+        final TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
+        if (ccHint != null) {
+            if (visible && !ccHintStr.isEmpty()) {
+                ccHint.setText(ccHintStr);
+                ccHint.setVisibility(View.VISIBLE);
+                if (animate) {
+                    ccHint.setAlpha(0f);
+                    ccHint.animate().alpha(1f)
+                                    .setDuration(SHOW_CLING_DURATION)
+                                    .start();
+                } else {
+                    ccHint.setAlpha(1f);
+                }
+            } else {
+                if (animate) {
+                    ccHint.animate().alpha(0f)
+                                    .setDuration(SHOW_CLING_DURATION)
+                                    .setListener(new AnimatorListenerAdapter() {
+                                        @Override
+                                        public void onAnimationEnd(Animator animation) {
+                                            ccHint.setVisibility(View.GONE);
+                                        }
+                                    })
+                                    .start();
+                } else {
+                    ccHint.setAlpha(0f);
+                    ccHint.setVisibility(View.GONE);
+                }
+            }
+        }
+    }
+
+    public void showFirstRunCling() {
+        if (isClingsEnabled() &&
+                !mSharedPrefs.getBoolean(Cling.FIRST_RUN_CLING_DISMISSED_KEY, false) &&
+                !skipCustomClingIfNoAccounts() ) {
+            // If we're not using the default workspace layout, replace workspace cling
+            // with a custom workspace cling (usually specified in an overlay)
+            // For now, only do this on tablets
+            if (!DISABLE_CUSTOM_CLINGS) {
+                if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
+                        getResources().getBoolean(R.bool.config_useCustomClings)) {
+                    // Use a custom cling
+                    View cling = findViewById(R.id.workspace_cling);
+                    ViewGroup clingParent = (ViewGroup) cling.getParent();
+                    int clingIndex = clingParent.indexOfChild(cling);
+                    clingParent.removeViewAt(clingIndex);
+                    View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
+                    clingParent.addView(customCling, clingIndex);
+                    customCling.setId(R.id.workspace_cling);
+                }
+            }
+            Cling cling = (Cling) findViewById(R.id.first_run_cling);
+            if (cling != null) {
+                String sbHintStr = getFirstRunClingSearchBarHint();
+                String ccHintStr = getFirstRunCustomContentHint();
+                if (!sbHintStr.isEmpty()) {
+                    TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+                    sbHint.setText(sbHintStr);
+                    sbHint.setVisibility(View.VISIBLE);
+                }
+                setCustomContentHintVisibility(cling, ccHintStr, true, false);
+            }
+            initCling(R.id.first_run_cling, 0, false, true);
+        } else {
+            removeCling(R.id.first_run_cling);
+        }
+    }
+
+    protected String getFirstRunClingSearchBarHint() {
+        return "";
+    }
+    protected String getFirstRunCustomContentHint() {
+        return "";
+    }
+    protected int getFirstRunFocusedHotseatAppDrawableId() {
+        return -1;
+    }
+    protected ComponentName getFirstRunFocusedHotseatAppComponentName() {
+        return null;
+    }
+    protected int getFirstRunFocusedHotseatAppRank() {
+        return -1;
+    }
+    protected String getFirstRunFocusedHotseatAppBubbleTitle() {
+        return "";
+    }
+    protected String getFirstRunFocusedHotseatAppBubbleDescription() {
+        return "";
+    }
+
+    public void showFirstRunWorkspaceCling() {
+        // Enable the clings only if they have not been dismissed before
+        if (isClingsEnabled() &&
+                !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
+            Cling c = initCling(R.id.workspace_cling, 0, false, true);
+
+            // Set the focused hotseat app if there is one
+            c.setFocusedHotseatApp(getFirstRunFocusedHotseatAppDrawableId(),
+                    getFirstRunFocusedHotseatAppRank(),
+                    getFirstRunFocusedHotseatAppComponentName(),
+                    getFirstRunFocusedHotseatAppBubbleTitle(),
+                    getFirstRunFocusedHotseatAppBubbleDescription());
+        } else {
+            removeCling(R.id.workspace_cling);
+        }
+    }
+    public Cling showFirstRunFoldersCling() {
+        // Enable the clings only if they have not been dismissed before
+        if (isClingsEnabled() &&
+                !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
+            Cling cling = initCling(R.id.folder_cling, R.id.cling_scrim,
+                    true, true);
+            return cling;
+        } else {
+            removeCling(R.id.folder_cling);
+            return null;
+        }
+    }
+    protected SharedPreferences getSharedPrefs() {
+        return mSharedPrefs;
+    }
+    public boolean isFolderClingVisible() {
+        Cling cling = (Cling) findViewById(R.id.folder_cling);
+        if (cling != null) {
+            return cling.getVisibility() == View.VISIBLE;
+        }
+        return false;
+    }
+    public void dismissFirstRunCling(View v) {
+        Cling cling = (Cling) findViewById(R.id.first_run_cling);
+        Runnable cb = new Runnable() {
+            public void run() {
+                // Show the workspace cling next
+                showFirstRunWorkspaceCling();
+            }
+        };
+        dismissCling(cling, cb, Cling.FIRST_RUN_CLING_DISMISSED_KEY,
+                DISMISS_CLING_DURATION, false);
+
+        // Fade out the search bar for the workspace cling coming up
+        mSearchDropTargetBar.hideSearchBar(true);
+    }
+    public void dismissWorkspaceCling(View v) {
+        Cling cling = (Cling) findViewById(R.id.workspace_cling);
+        Runnable cb = null;
+        if (v == null) {
+            cb = new Runnable() {
+                public void run() {
+                    mWorkspace.enterOverviewMode();
+                }
+            };
+        }
+        dismissCling(cling, cb, Cling.WORKSPACE_CLING_DISMISSED_KEY,
+                DISMISS_CLING_DURATION, true);
+
+        // Fade in the search bar
+        mSearchDropTargetBar.showSearchBar(true);
+    }
+    public void dismissFolderCling(View v) {
+        Cling cling = (Cling) findViewById(R.id.folder_cling);
+        dismissCling(cling, null, Cling.FOLDER_CLING_DISMISSED_KEY,
+                DISMISS_CLING_DURATION, true);
+    }
+
+    /**
+     * Prints out out state for debugging.
+     */
+    public void dumpState() {
+        Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
+        Log.d(TAG, "mSavedState=" + mSavedState);
+        Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
+        Log.d(TAG, "mRestoring=" + mRestoring);
+        Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
+        Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
+        Log.d(TAG, "sFolders.size=" + sFolders.size());
+        mModel.dumpState();
+
+        if (mAppsCustomizeContent != null) {
+            mAppsCustomizeContent.dumpState();
+        }
+        Log.d(TAG, "END launcher3 dump state");
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        synchronized (sDumpLogs) {
+            writer.println(" ");
+            writer.println("Debug logs: ");
+            for (int i = 0; i < sDumpLogs.size(); i++) {
+                writer.println("  " + sDumpLogs.get(i));
+            }
+        }
+    }
+
+    public static void dumpDebugLogsToConsole() {
+        if (DEBUG_DUMP_LOG) {
+            synchronized (sDumpLogs) {
+                Log.d(TAG, "");
+                Log.d(TAG, "*********************");
+                Log.d(TAG, "Launcher debug logs: ");
+                for (int i = 0; i < sDumpLogs.size(); i++) {
+                    Log.d(TAG, "  " + sDumpLogs.get(i));
+                }
+                Log.d(TAG, "*********************");
+                Log.d(TAG, "");
+            }
+        }
+    }
+
+    public static void addDumpLog(String tag, String log, boolean debugLog) {
+        if (debugLog) {
+            Log.d(tag, log);
+        }
+        if (DEBUG_DUMP_LOG) {
+            sDateStamp.setTime(System.currentTimeMillis());
+            synchronized (sDumpLogs) {
+                sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log);
+            }
+        }
+    }
+
+    public void dumpLogsToLocalData() {
+        if (DEBUG_DUMP_LOG) {
+            new Thread("DumpLogsToLocalData") {
+                @Override
+                public void run() {
+                    boolean success = false;
+                    sDateStamp.setTime(sRunStart);
+                    String FILENAME = sDateStamp.getMonth() + "-"
+                            + sDateStamp.getDay() + "_"
+                            + sDateStamp.getHours() + "-"
+                            + sDateStamp.getMinutes() + "_"
+                            + sDateStamp.getSeconds() + ".txt";
+
+                    FileOutputStream fos = null;
+                    File outFile = null;
+                    try {
+                        outFile = new File(getFilesDir(), FILENAME);
+                        outFile.createNewFile();
+                        fos = new FileOutputStream(outFile);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    if (fos != null) {
+                        PrintWriter writer = new PrintWriter(fos);
+
+                        writer.println(" ");
+                        writer.println("Debug logs: ");
+                        synchronized (sDumpLogs) {
+                            for (int i = 0; i < sDumpLogs.size(); i++) {
+                                writer.println("  " + sDumpLogs.get(i));
+                            }
+                        }
+                        writer.close();
+                    }
+                    try {
+                        if (fos != null) {
+                            fos.close();
+                            success = true;
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }.start();
+        }
+    }
+}
+
+interface LauncherTransitionable {
+    View getContent();
+    void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
+    void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
+    void onLauncherTransitionStep(Launcher l, float t);
+    void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
+}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
new file mode 100644
index 0000000..01f72a7
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -0,0 +1,129 @@
+/*
+ * 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import java.util.HashSet;
+
+public class LauncherAnimUtils {
+    static HashSet<Animator> sAnimators = new HashSet<Animator>();
+    static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
+        public void onAnimationStart(Animator animation) {
+        }
+
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        public void onAnimationEnd(Animator animation) {
+            sAnimators.remove(animation);
+        }
+
+        public void onAnimationCancel(Animator animation) {
+            sAnimators.remove(animation);
+        }
+    };
+
+    public static void cancelOnDestroyActivity(Animator a) {
+        sAnimators.add(a);
+        a.addListener(sEndAnimListener);
+    }
+
+    // Helper method. Assumes a draw is pending, and that if the animation's duration is 0
+    // it should be cancelled
+    public static void startAnimationAfterNextDraw(final Animator animator, final View view) {
+        view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+                private boolean mStarted = false;
+                public void onDraw() {
+                    if (mStarted) return;
+                    mStarted = true;
+                    // Use this as a signal that the animation was cancelled
+                    if (animator.getDuration() == 0) {
+                        return;
+                    }
+                    animator.start();
+
+                    final ViewTreeObserver.OnDrawListener listener = this;
+                    view.post(new Runnable() {
+                            public void run() {
+                                view.getViewTreeObserver().removeOnDrawListener(listener);
+                            }
+                        });
+                }
+            });
+    }
+
+    public static void onDestroyActivity() {
+        HashSet<Animator> animators = new HashSet<Animator>(sAnimators);
+        for (Animator a : animators) {
+            if (a.isRunning()) {
+                a.cancel();
+            } else {
+                sAnimators.remove(a);
+            }
+        }
+    }
+
+    public static AnimatorSet createAnimatorSet() {
+        AnimatorSet anim = new AnimatorSet();
+        cancelOnDestroyActivity(anim);
+        return anim;
+    }
+
+    public static ValueAnimator ofFloat(View target, float... values) {
+        ValueAnimator anim = new ValueAnimator();
+        anim.setFloatValues(values);
+        cancelOnDestroyActivity(anim);
+        return anim;
+    }
+
+    public static ObjectAnimator ofFloat(View target, String propertyName, float... values) {
+        ObjectAnimator anim = new ObjectAnimator();
+        anim.setTarget(target);
+        anim.setPropertyName(propertyName);
+        anim.setFloatValues(values);
+        cancelOnDestroyActivity(anim);
+        new FirstFrameAnimatorHelper(anim, target);
+        return anim;
+    }
+
+    public static ObjectAnimator ofPropertyValuesHolder(View target,
+            PropertyValuesHolder... values) {
+        ObjectAnimator anim = new ObjectAnimator();
+        anim.setTarget(target);
+        anim.setValues(values);
+        cancelOnDestroyActivity(anim);
+        new FirstFrameAnimatorHelper(anim, target);
+        return anim;
+    }
+
+    public static ObjectAnimator ofPropertyValuesHolder(Object target,
+            View view, PropertyValuesHolder... values) {
+        ObjectAnimator anim = new ObjectAnimator();
+        anim.setTarget(target);
+        anim.setValues(values);
+        cancelOnDestroyActivity(anim);
+        new FirstFrameAnimatorHelper(anim, view);
+        return anim;
+    }
+}
diff --git a/src/com/android/launcher3/LauncherAnimatorUpdateListener.java b/src/com/android/launcher3/LauncherAnimatorUpdateListener.java
new file mode 100644
index 0000000..ec9fd4d
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAnimatorUpdateListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+
+abstract class LauncherAnimatorUpdateListener implements AnimatorUpdateListener {
+    public void onAnimationUpdate(ValueAnimator animation) {
+        final float b = (Float) animation.getAnimatedValue();
+        final float a = 1f - b;
+        onAnimationUpdate(a, b);
+    }
+
+    abstract void onAnimationUpdate(float a, float b);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
new file mode 100644
index 0000000..a255b89
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -0,0 +1,219 @@
+/*
+ * 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.launcher3;
+
+import android.app.SearchManager;
+import android.content.*;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+
+import java.lang.ref.WeakReference;
+
+public class LauncherAppState {
+    private static final String TAG = "LauncherAppState";
+    private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
+
+    private LauncherModel mModel;
+    private IconCache mIconCache;
+    private AppFilter mAppFilter;
+    private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
+    private boolean mIsScreenLarge;
+    private float mScreenDensity;
+    private int mLongPressTimeout = 300;
+
+    private static WeakReference<LauncherProvider> sLauncherProvider;
+    private static Context sContext;
+
+    private static LauncherAppState INSTANCE;
+
+    private DynamicGrid mDynamicGrid;
+
+    public static LauncherAppState getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = new LauncherAppState();
+        }
+        return INSTANCE;
+    }
+
+    public static LauncherAppState getInstanceNoCreate() {
+        return INSTANCE;
+    }
+
+    public Context getContext() {
+        return sContext;
+    }
+
+    public static void setApplicationContext(Context context) {
+        if (sContext != null) {
+            Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context);
+        }
+        sContext = context.getApplicationContext();
+    }
+
+    private LauncherAppState() {
+        if (sContext == null) {
+            throw new IllegalStateException("LauncherAppState inited before app context set");
+        }
+
+        Log.v(Launcher.TAG, "LauncherAppState inited");
+
+        if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
+            MemoryTracker.startTrackingMe(sContext, "L");
+        }
+
+        // set sIsScreenXLarge and mScreenDensity *before* creating icon cache
+        mIsScreenLarge = isScreenLarge(sContext.getResources());
+        mScreenDensity = sContext.getResources().getDisplayMetrics().density;
+
+        mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
+        mIconCache = new IconCache(sContext);
+
+        mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
+        mModel = new LauncherModel(this, mIconCache, mAppFilter);
+
+        // Register intent receivers
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        sContext.registerReceiver(mModel, filter);
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+        sContext.registerReceiver(mModel, filter);
+        filter = new IntentFilter();
+        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+        sContext.registerReceiver(mModel, filter);
+        filter = new IntentFilter();
+        filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+        sContext.registerReceiver(mModel, filter);
+
+        // Register for changes to the favorites
+        ContentResolver resolver = sContext.getContentResolver();
+        resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
+                mFavoritesObserver);
+    }
+
+    /**
+     * Call from Application.onTerminate(), which is not guaranteed to ever be called.
+     */
+    public void onTerminate() {
+        sContext.unregisterReceiver(mModel);
+
+        ContentResolver resolver = sContext.getContentResolver();
+        resolver.unregisterContentObserver(mFavoritesObserver);
+    }
+
+    /**
+     * Receives notifications whenever the user favorites have changed.
+     */
+    private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            // If the database has ever changed, then we really need to force a reload of the
+            // workspace on the next load
+            mModel.resetLoadedState(false, true);
+            mModel.startLoaderFromBackground();
+        }
+    };
+
+    LauncherModel setLauncher(Launcher launcher) {
+        if (mModel == null) {
+            throw new IllegalStateException("setLauncher() called before init()");
+        }
+        mModel.initialize(launcher);
+        return mModel;
+    }
+
+    IconCache getIconCache() {
+        return mIconCache;
+    }
+
+    LauncherModel getModel() {
+        return mModel;
+    }
+
+    boolean shouldShowAppOrWidgetProvider(ComponentName componentName) {
+        return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
+    }
+
+    WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() {
+        return mWidgetPreviewCacheDb;
+    }
+
+    static void setLauncherProvider(LauncherProvider provider) {
+        sLauncherProvider = new WeakReference<LauncherProvider>(provider);
+    }
+
+    static LauncherProvider getLauncherProvider() {
+        return sLauncherProvider.get();
+    }
+
+    public static String getSharedPreferencesKey() {
+        return SHARED_PREFERENCES_KEY;
+    }
+
+    DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
+                                  int width, int height,
+                                  int availableWidth, int availableHeight) {
+        if (mDynamicGrid == null) {
+            mDynamicGrid = new DynamicGrid(context,
+                    context.getResources(),
+                    minWidth, minHeight, width, height,
+                    availableWidth, availableHeight);
+        }
+
+        // Update the icon size
+        DeviceProfile grid = mDynamicGrid.getDeviceProfile();
+        Utilities.setIconSize(grid.iconSizePx);
+        grid.updateFromConfiguration(context.getResources(), width, height,
+                availableWidth, availableHeight);
+        return grid;
+    }
+    DynamicGrid getDynamicGrid() {
+        return mDynamicGrid;
+    }
+
+    public boolean isScreenLarge() {
+        return mIsScreenLarge;
+    }
+
+    // Need a version that doesn't require an instance of LauncherAppState for the wallpaper picker
+    public static boolean isScreenLarge(Resources res) {
+        return res.getBoolean(R.bool.is_large_tablet);
+    }
+
+    public static boolean isScreenLandscape(Context context) {
+        return context.getResources().getConfiguration().orientation ==
+            Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    public float getScreenDensity() {
+        return mScreenDensity;
+    }
+
+    public int getLongPressTimeout() {
+        return mLongPressTimeout;
+    }
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
new file mode 100644
index 0000000..7b08d44
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 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.launcher3;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+
+/**
+ * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
+ * which correctly captures all long-press events. This ensures that users can
+ * always pick up and move widgets.
+ */
+public class LauncherAppWidgetHost extends AppWidgetHost {
+
+    Launcher mLauncher;
+
+    public LauncherAppWidgetHost(Launcher launcher, int hostId) {
+        super(launcher, hostId);
+        mLauncher = launcher;
+    }
+
+    @Override
+    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+            AppWidgetProviderInfo appWidget) {
+        return new LauncherAppWidgetHostView(context);
+    }
+
+    @Override
+    public void stopListening() {
+        super.stopListening();
+        clearViews();
+    }
+
+    protected void onProvidersChanged() {
+        // Once we get the message that widget packages are updated, we need to rebind items
+        // in AppsCustomize accordingly.
+        mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher));
+    }
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
new file mode 100644
index 0000000..83aef1a
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2009 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.launcher3;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DragLayer.TouchCompleteListener;
+
+/**
+ * {@inheritDoc}
+ */
+public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
+    private CheckLongPressHelper mLongPressHelper;
+    private LayoutInflater mInflater;
+    private Context mContext;
+    private int mPreviousOrientation;
+    private DragLayer mDragLayer;
+
+    public LauncherAppWidgetHostView(Context context) {
+        super(context);
+        mContext = context;
+        mLongPressHelper = new CheckLongPressHelper(this);
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mDragLayer = ((Launcher) context).getDragLayer();
+    }
+
+    @Override
+    protected View getErrorView() {
+        return mInflater.inflate(R.layout.appwidget_error, this, false);
+    }
+
+    @Override
+    public void updateAppWidget(RemoteViews remoteViews) {
+        // Store the orientation in which the widget was inflated
+        mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
+        super.updateAppWidget(remoteViews);
+    }
+
+    public boolean orientationChangedSincedInflation() {
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        if (mPreviousOrientation != orientation) {
+           return true;
+       }
+       return false;
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // Consume any touch events for ourselves after longpress is triggered
+        if (mLongPressHelper.hasPerformedLongPress()) {
+            mLongPressHelper.cancelLongPress();
+            return true;
+        }
+
+        // Watch for longpress events at this level to make sure
+        // users can always pick up this widget
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                mLongPressHelper.postCheckForLongPress();
+                mDragLayer.setTouchCompleteListener(this);
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mLongPressHelper.cancelLongPress();
+                break;
+        }
+
+        // Otherwise continue letting touch events fall through to children
+        return false;
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        // If the widget does not handle touch, then cancel
+        // long press when we release the touch
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mLongPressHelper.cancelLongPress();
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+        mLongPressHelper.cancelLongPress();
+    }
+
+    @Override
+    public void onTouchComplete() {
+        mLongPressHelper.cancelLongPress();
+    }
+
+    @Override
+    public int getDescendantFocusability() {
+        return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+    }
+
+
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
new file mode 100644
index 0000000..28df90f
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009 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.launcher3;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.ContentValues;
+
+/**
+ * Represents a widget (either instantiated or about to be) in the Launcher.
+ */
+class LauncherAppWidgetInfo extends ItemInfo {
+
+    /**
+     * Indicates that the widget hasn't been instantiated yet.
+     */
+    static final int NO_ID = -1;
+
+    /**
+     * Identifier for this widget when talking with
+     * {@link android.appwidget.AppWidgetManager} for updates.
+     */
+    int appWidgetId = NO_ID;
+
+    ComponentName providerName;
+
+    // TODO: Are these necessary here?
+    int minWidth = -1;
+    int minHeight = -1;
+
+    private boolean mHasNotifiedInitialWidgetSizeChanged;
+
+    /**
+     * View that holds this widget after it's been created.  This view isn't created
+     * until Launcher knows it's needed.
+     */
+    AppWidgetHostView hostView = null;
+
+    LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+        this.appWidgetId = appWidgetId;
+        this.providerName = providerName;
+
+        // Since the widget isn't instantiated yet, we don't know these values. Set them to -1
+        // to indicate that they should be calculated based on the layout and minWidth/minHeight
+        spanX = -1;
+        spanY = -1;
+    }
+
+    @Override
+    void onAddToDatabase(ContentValues values) {
+        super.onAddToDatabase(values);
+        values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
+        values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
+    }
+
+    /**
+     * When we bind the widget, we should notify the widget that the size has changed if we have not
+     * done so already (only really for default workspace widgets).
+     */
+    void onBindAppWidget(Launcher launcher) {
+        if (!mHasNotifiedInitialWidgetSizeChanged) {
+            notifyWidgetSizeChanged(launcher);
+        }
+    }
+
+    /**
+     * Trigger an update callback to the widget to notify it that its size has changed.
+     */
+    void notifyWidgetSizeChanged(Launcher launcher) {
+        AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
+        mHasNotifiedInitialWidgetSizeChanged = true;
+    }
+
+    @Override
+    public String toString() {
+        return "AppWidget(id=" + Integer.toString(appWidgetId) + ")";
+    }
+
+    @Override
+    void unbind() {
+        super.unbind();
+        hostView = null;
+    }
+}
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
new file mode 100644
index 0000000..8b179f1
--- /dev/null
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -0,0 +1,34 @@
+/*
+ * 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.launcher3;
+
+import android.app.Application;
+
+public class LauncherApplication extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        LauncherAppState.setApplicationContext(this);
+        LauncherAppState.getInstance();
+    }
+
+    @Override
+    public void onTerminate() {
+        super.onTerminate();
+        LauncherAppState.getInstance().onTerminate();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
new file mode 100644
index 0000000..2b5059b
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.launcher3;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupManager;
+import android.content.Context;
+
+public class LauncherBackupAgentHelper extends BackupAgentHelper {
+
+    private static BackupManager sBackupManager;
+
+    /**
+     * Notify the backup manager that out database is dirty.
+     *
+     * <P>This does not force an immediate backup.
+     *
+     * @param context application context
+     */
+    public static void dataChanged(Context context) {
+        if (sBackupManager == null) {
+            sBackupManager = new BackupManager(context);
+        }
+        sBackupManager.dataChanged();
+    }
+
+
+    @Override
+    public void onCreate() {
+        addHelper(LauncherBackupHelper.LAUNCHER_PREFIX, new LauncherBackupHelper(this));
+    }
+}
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
new file mode 100644
index 0000000..9b901ee
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -0,0 +1,1016 @@
+/*
+ * 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.launcher3;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.backup.BackupProtos;
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Resource;
+import com.android.launcher3.backup.BackupProtos.Screen;
+import com.android.launcher3.backup.BackupProtos.Widget;
+
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+/**
+ * Persist the launcher home state across calamities.
+ */
+public class LauncherBackupHelper implements BackupHelper {
+
+    private static final String TAG = "LauncherBackupHelper";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_PAYLOAD = false;
+
+    private static final int MAX_JOURNAL_SIZE = 1000000;
+
+    /** icons are large, dribble them out */
+    private static final int MAX_ICONS_PER_PASS = 10;
+
+    /** widgets contain previews, which are very large, dribble them out */
+    private static final int MAX_WIDGETS_PER_PASS = 5;
+
+    public static final int IMAGE_COMPRESSION_QUALITY = 75;
+
+    public static final String LAUNCHER_PREFIX = "L";
+
+    private static final Bitmap.CompressFormat IMAGE_FORMAT =
+            android.graphics.Bitmap.CompressFormat.PNG;
+
+    private static BackupManager sBackupManager;
+
+    private static final String[] FAVORITE_PROJECTION = {
+            Favorites._ID,                     // 0
+            Favorites.MODIFIED,                // 1
+            Favorites.INTENT,                  // 2
+            Favorites.APPWIDGET_PROVIDER,      // 3
+            Favorites.APPWIDGET_ID,            // 4
+            Favorites.CELLX,                   // 5
+            Favorites.CELLY,                   // 6
+            Favorites.CONTAINER,               // 7
+            Favorites.ICON,                    // 8
+            Favorites.ICON_PACKAGE,            // 9
+            Favorites.ICON_RESOURCE,           // 10
+            Favorites.ICON_TYPE,               // 11
+            Favorites.ITEM_TYPE,               // 12
+            Favorites.SCREEN,                  // 13
+            Favorites.SPANX,                   // 14
+            Favorites.SPANY,                   // 15
+            Favorites.TITLE,                   // 16
+    };
+
+    private static final int ID_INDEX = 0;
+    private static final int ID_MODIFIED = 1;
+    private static final int INTENT_INDEX = 2;
+    private static final int APPWIDGET_PROVIDER_INDEX = 3;
+    private static final int APPWIDGET_ID_INDEX = 4;
+    private static final int CELLX_INDEX = 5;
+    private static final int CELLY_INDEX = 6;
+    private static final int CONTAINER_INDEX = 7;
+    private static final int ICON_INDEX = 8;
+    private static final int ICON_PACKAGE_INDEX = 9;
+    private static final int ICON_RESOURCE_INDEX = 10;
+    private static final int ICON_TYPE_INDEX = 11;
+    private static final int ITEM_TYPE_INDEX = 12;
+    private static final int SCREEN_INDEX = 13;
+    private static final int SPANX_INDEX = 14;
+    private static final int SPANY_INDEX = 15;
+    private static final int TITLE_INDEX = 16;
+
+    private static final String[] SCREEN_PROJECTION = {
+            WorkspaceScreens._ID,              // 0
+            WorkspaceScreens.MODIFIED,         // 1
+            WorkspaceScreens.SCREEN_RANK       // 2
+    };
+
+    private static final int SCREEN_RANK_INDEX = 2;
+
+    private final Context mContext;
+
+    private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
+
+    private ArrayList<Key> mKeys;
+
+    public LauncherBackupHelper(Context context) {
+        mContext = context;
+    }
+
+    private void dataChanged() {
+        if (sBackupManager == null) {
+            sBackupManager = new BackupManager(mContext);
+        }
+        sBackupManager.dataChanged();
+    }
+
+    /**
+     * Back up launcher data so we can restore the user's state on a new device.
+     *
+     * <P>The journal is a timestamp and a list of keys that were saved as of that time.
+     *
+     * <P>Keys may come back in any order, so each key/value is one complete row of the database.
+     *
+     * @param oldState notes from the last backup
+     * @param data incremental key/value pairs to persist off-device
+     * @param newState notes for the next backup
+     * @throws IOException
+     */
+    @Override
+    public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) {
+        Log.v(TAG, "onBackup");
+
+        Journal in = readJournal(oldState);
+        Journal out = new Journal();
+
+        long lastBackupTime = in.t;
+        out.t = System.currentTimeMillis();
+        out.rows = 0;
+        out.bytes = 0;
+
+        Log.v(TAG, "lastBackupTime=" + lastBackupTime);
+
+        ArrayList<Key> keys = new ArrayList<Key>();
+        try {
+            backupFavorites(in, data, out, keys);
+            backupScreens(in, data, out, keys);
+            backupIcons(in, data, out, keys);
+            backupWidgets(in, data, out, keys);
+        } catch (IOException e) {
+            Log.e(TAG, "launcher backup has failed", e);
+        }
+
+        out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+        writeJournal(newState, out);
+        Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
+    }
+
+    /**
+     * Restore launcher configuration from the restored data stream.
+     *
+     * <P>Keys may arrive in any order.
+     *
+     * @param data the key/value pair from the server
+     */
+    @Override
+    public void restoreEntity(BackupDataInputStream data) {
+        Log.v(TAG, "restoreEntity");
+        if (mKeys == null) {
+            mKeys = new ArrayList<Key>();
+        }
+        byte[] buffer = new byte[512];
+            String backupKey = data.getKey();
+            int dataSize = data.size();
+            if (buffer.length < dataSize) {
+                buffer = new byte[dataSize];
+            }
+            Key key = null;
+        int bytesRead = 0;
+        try {
+            bytesRead = data.read(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
+        } catch (IOException e) {
+            Log.d(TAG, "failed to read entity from restore data", e);
+        }
+        try {
+            key = backupKeyToKey(backupKey);
+            switch (key.type) {
+                case Key.FAVORITE:
+                    restoreFavorite(key, buffer, dataSize, mKeys);
+                    break;
+
+                case Key.SCREEN:
+                    restoreScreen(key, buffer, dataSize, mKeys);
+                    break;
+
+                case Key.ICON:
+                    restoreIcon(key, buffer, dataSize, mKeys);
+                    break;
+
+                case Key.WIDGET:
+                    restoreWidget(key, buffer, dataSize, mKeys);
+                    break;
+
+                default:
+                    Log.w(TAG, "unknown restore entity type: " + key.type);
+                    break;
+            }
+        } catch (KeyParsingException e) {
+            Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
+        }
+
+    }
+
+    /**
+     * Record the restore state for the next backup.
+     *
+     * @param newState notes about the backup state after restore.
+     */
+    @Override
+    public void writeNewStateDescription(ParcelFileDescriptor newState) {
+        // clear the output journal time, to force a full backup to
+        // will catch any changes the restore process might have made
+        Journal out = new Journal();
+        out.t = 0;
+        out.key = mKeys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+        writeJournal(newState, out);
+        Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
+        mKeys.clear();
+    }
+
+    /**
+     * Write all modified favorites to the data stream.
+     *
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup
+     * @throws IOException
+     */
+    private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys)
+            throws IOException {
+        // read the old ID set
+        Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
+        if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+        // persist things that have changed since the last backup
+        ContentResolver cr = mContext.getContentResolver();
+        Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+                null, null, null);
+        Set<String> currentIds = new HashSet<String>(cursor.getCount());
+        try {
+            cursor.moveToPosition(-1);
+            while(cursor.moveToNext()) {
+                final long id = cursor.getLong(ID_INDEX);
+                final long updateTime = cursor.getLong(ID_MODIFIED);
+                Key key = getKey(Key.FAVORITE, id);
+                keys.add(key);
+                currentIds.add(keyToBackupKey(key));
+                if (updateTime > in.t) {
+                    byte[] blob = packFavorite(cursor);
+                    writeRowToBackup(key, blob, out, data);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        out.rows += removeDeletedKeysFromBackup(savedIds, data);
+    }
+
+    /**
+     * Read a favorite from the stream.
+     *
+     * <P>Keys arrive in any order, so screens and containers may not exist yet.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
+        try {
+            Favorite favorite =  unpackFavorite(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /**
+     * Write all modified screens to the data stream.
+     *
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup
+     * @throws IOException
+     */
+    private void backupScreens(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys)
+            throws IOException {
+        // read the old ID set
+        Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
+        if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
+
+        // persist things that have changed since the last backup
+        ContentResolver cr = mContext.getContentResolver();
+        Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
+                null, null, null);
+        Set<String> currentIds = new HashSet<String>(cursor.getCount());
+        try {
+            cursor.moveToPosition(-1);
+            while(cursor.moveToNext()) {
+                final long id = cursor.getLong(ID_INDEX);
+                final long updateTime = cursor.getLong(ID_MODIFIED);
+                Key key = getKey(Key.SCREEN, id);
+                keys.add(key);
+                currentIds.add(keyToBackupKey(key));
+                if (updateTime > in.t) {
+                    byte[] blob = packScreen(cursor);
+                    writeRowToBackup(key, blob, out, data);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        out.rows += removeDeletedKeysFromBackup(savedIds, data);
+    }
+
+    /**
+     * Read a screen from the stream.
+     *
+     * <P>Keys arrive in any order, so children of this screen may already exist.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking screen " + key.id);
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+        try {
+            Screen screen = unpackScreen(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /**
+     * Write all the static icon resources we need to render placeholders
+     * for a package that is not installed.
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup
+     * @throws IOException
+     */
+    private void backupIcons(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys) throws IOException {
+        // persist icons that haven't been persisted yet
+        final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        if (appState == null) {
+            dataChanged(); // try again later
+            if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
+            return;
+        }
+        final ContentResolver cr = mContext.getContentResolver();
+        final IconCache iconCache = appState.getIconCache();
+        final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+
+        // read the old ID set
+        Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
+        if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
+
+        int startRows = out.rows;
+        if (DEBUG) Log.d(TAG, "starting here: " + startRows);
+        String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION;
+        Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+                where, null, null);
+        Set<String> currentIds = new HashSet<String>(cursor.getCount());
+        try {
+            cursor.moveToPosition(-1);
+            while(cursor.moveToNext()) {
+                final long id = cursor.getLong(ID_INDEX);
+                final String intentDescription = cursor.getString(INTENT_INDEX);
+                try {
+                    Intent intent = Intent.parseUri(intentDescription, 0);
+                    ComponentName cn = intent.getComponent();
+                    Key key = null;
+                    String backupKey = null;
+                    if (cn != null) {
+                        key = getKey(Key.ICON, cn.flattenToShortString());
+                        backupKey = keyToBackupKey(key);
+                        currentIds.add(backupKey);
+                    } else {
+                        Log.w(TAG, "empty intent on application favorite: " + id);
+                    }
+                    if (savedIds.contains(backupKey)) {
+                        if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
+
+                        // remember that we already backed this up previously
+                        keys.add(key);
+                    } else if (backupKey != null) {
+                        if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
+                        if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
+                            if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
+                            Bitmap icon = iconCache.getIcon(intent);
+                            keys.add(key);
+                            if (icon != null && !iconCache.isDefaultIcon(icon)) {
+                                byte[] blob = packIcon(dpi, icon);
+                                writeRowToBackup(key, blob, out, data);
+                            }
+                        } else {
+                            if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey);
+                            // too many icons for this pass, request another.
+                            dataChanged();
+                        }
+                    }
+                } catch (URISyntaxException e) {
+                    Log.w(TAG, "invalid URI on application favorite: " + id);
+                } catch (IOException e) {
+                    Log.w(TAG, "unable to save application icon for favorite: " + id);
+                }
+
+            }
+        } finally {
+            cursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        out.rows += removeDeletedKeysFromBackup(savedIds, data);
+    }
+
+    /**
+     * Read an icon from the stream.
+     *
+     * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking icon " + key.id);
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+        try {
+            Resource res = unpackIcon(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + res.dpi);
+            if (DEBUG) Log.d(TAG, "read " +
+                    Base64.encodeToString(res.data, 0, res.data.length,
+                            Base64.NO_WRAP));
+            Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
+            if (icon == null) {
+                Log.w(TAG, "failed to unpack icon for " + key.name);
+            }
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /**
+     * Write all the static widget resources we need to render placeholders
+     * for a package that is not installed.
+     *
+     * @param in notes from last backup
+     * @param data output stream for key/value pairs
+     * @param out notes about this backup
+     * @param keys keys to mark as clean in the notes for next backup
+     * @throws IOException
+     */
+    private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
+            ArrayList<Key> keys) throws IOException {
+        // persist static widget info that hasn't been persisted yet
+        final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        if (appState == null) {
+            dataChanged(); // try again later
+            if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
+            return;
+        }
+        final ContentResolver cr = mContext.getContentResolver();
+        final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
+        final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
+        final IconCache iconCache = appState.getIconCache();
+        final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+        final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
+        if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
+
+        // read the old ID set
+        Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
+        if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
+
+        int startRows = out.rows;
+        if (DEBUG) Log.d(TAG, "starting here: " + startRows);
+        String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
+        Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+                where, null, null);
+        Set<String> currentIds = new HashSet<String>(cursor.getCount());
+        try {
+            cursor.moveToPosition(-1);
+            while(cursor.moveToNext()) {
+                final long id = cursor.getLong(ID_INDEX);
+                final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
+                final int spanX = cursor.getInt(SPANX_INDEX);
+                final int spanY = cursor.getInt(SPANY_INDEX);
+                final ComponentName provider = ComponentName.unflattenFromString(providerName);
+                Key key = null;
+                String backupKey = null;
+                if (provider != null) {
+                    key = getKey(Key.WIDGET, providerName);
+                    backupKey = keyToBackupKey(key);
+                    currentIds.add(backupKey);
+                } else {
+                    Log.w(TAG, "empty intent on appwidget: " + id);
+                }
+                if (savedIds.contains(backupKey)) {
+                    if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
+
+                    // remember that we already backed this up previously
+                    keys.add(key);
+                } else if (backupKey != null) {
+                    if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
+                    if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
+                        if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
+                        previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
+                                spanY * profile.cellHeightPx, widgetSpacingLayout);
+                        byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
+                        keys.add(key);
+                        writeRowToBackup(key, blob, out, data);
+
+                    } else {
+                        if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey);
+                        // too many widgets for this pass, request another.
+                        dataChanged();
+                    }
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
+
+        // these IDs must have been deleted
+        savedIds.removeAll(currentIds);
+        out.rows += removeDeletedKeysFromBackup(savedIds, data);
+    }
+
+    /**
+     * Read a widget from the stream.
+     *
+     * <P>Keys arrive in any order, so widgets that use this data may already exist.
+     *
+     * @param key identifier for the row
+     * @param buffer the serialized proto from the stream, may be larger than dataSize
+     * @param dataSize the size of the proto from the stream
+     * @param keys keys to mark as clean in the notes for next backup
+     */
+    private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+        Log.v(TAG, "unpacking widget " + key.id);
+        if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+                Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+        try {
+            Widget widget = unpackWidget(buffer, 0, dataSize);
+            if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
+            if (widget.icon.data != null)  {
+                Bitmap icon = BitmapFactory
+                        .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
+                if (icon == null) {
+                    Log.w(TAG, "failed to unpack widget icon for " + key.name);
+                }
+            }
+        } catch (InvalidProtocolBufferNanoException e) {
+            Log.w(TAG, "failed to decode proto", e);
+        }
+    }
+
+    /** create a new key, with an integer ID.
+     *
+     * <P> Keys contain their own checksum instead of using
+     * the heavy-weight CheckedMessage wrapper.
+     */
+    private Key getKey(int type, long id) {
+        Key key = new Key();
+        key.type = type;
+        key.id = id;
+        key.checksum = checkKey(key);
+        return key;
+    }
+
+    /** create a new key for a named object.
+     *
+     * <P> Keys contain their own checksum instead of using
+     * the heavy-weight CheckedMessage wrapper.
+     */
+    private Key getKey(int type, String name) {
+        Key key = new Key();
+        key.type = type;
+        key.name = name;
+        key.checksum = checkKey(key);
+        return key;
+    }
+
+    /** keys need to be strings, serialize and encode. */
+    private String keyToBackupKey(Key key) {
+        return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
+    }
+
+    /** keys need to be strings, decode and parse. */
+    private Key backupKeyToKey(String backupKey) throws KeyParsingException {
+        try {
+            Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
+            if (key.checksum != checkKey(key)) {
+                key = null;
+                throw new KeyParsingException("invalid key read from stream" + backupKey);
+            }
+            return key;
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new KeyParsingException(e);
+        } catch (IllegalArgumentException e) {
+            throw new KeyParsingException(e);
+        }
+    }
+
+    private String getKeyName(Key key) {
+        if (TextUtils.isEmpty(key.name)) {
+            return Long.toString(key.id);
+        } else {
+            return key.name;
+        }
+
+    }
+
+    private String geKeyType(Key key) {
+        switch (key.type) {
+            case Key.FAVORITE:
+                return "favorite";
+            case Key.SCREEN:
+                return "screen";
+            case Key.ICON:
+                return "icon";
+            case Key.WIDGET:
+                return "widget";
+            default:
+                return "anonymous";
+        }
+    }
+
+    /** Compute the checksum over the important bits of a key. */
+    private long checkKey(Key key) {
+        CRC32 checksum = new CRC32();
+        checksum.update(key.type);
+        checksum.update((int) (key.id & 0xffff));
+        checksum.update((int) ((key.id >> 32) & 0xffff));
+        if (!TextUtils.isEmpty(key.name)) {
+            checksum.update(key.name.getBytes());
+        }
+        return checksum.getValue();
+    }
+
+    /** Serialize a Favorite for persistence, including a checksum wrapper. */
+    private byte[] packFavorite(Cursor c) {
+        Favorite favorite = new Favorite();
+        favorite.id = c.getLong(ID_INDEX);
+        favorite.screen = c.getInt(SCREEN_INDEX);
+        favorite.container = c.getInt(CONTAINER_INDEX);
+        favorite.cellX = c.getInt(CELLX_INDEX);
+        favorite.cellY = c.getInt(CELLY_INDEX);
+        favorite.spanX = c.getInt(SPANX_INDEX);
+        favorite.spanY = c.getInt(SPANY_INDEX);
+        favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+        if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+            String iconPackage = c.getString(ICON_PACKAGE_INDEX);
+            if (!TextUtils.isEmpty(iconPackage)) {
+                favorite.iconPackage = iconPackage;
+            }
+            String iconResource = c.getString(ICON_RESOURCE_INDEX);
+            if (!TextUtils.isEmpty(iconResource)) {
+                favorite.iconResource = iconResource;
+            }
+        }
+        if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+            byte[] blob = c.getBlob(ICON_INDEX);
+            if (blob != null && blob.length > 0) {
+                favorite.icon = blob;
+            }
+        }
+        String title = c.getString(TITLE_INDEX);
+        if (!TextUtils.isEmpty(title)) {
+            favorite.title = title;
+        }
+        String intent = c.getString(INTENT_INDEX);
+        if (!TextUtils.isEmpty(intent)) {
+            favorite.intent = intent;
+        }
+        favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
+        if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+            favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
+            String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
+            if (!TextUtils.isEmpty(appWidgetProvider)) {
+                favorite.appWidgetProvider = appWidgetProvider;
+            }
+        }
+
+        return writeCheckedBytes(favorite);
+    }
+
+    /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
+    private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Favorite favorite = new Favorite();
+        MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
+        return favorite;
+    }
+
+    /** Serialize a Screen for persistence, including a checksum wrapper. */
+    private byte[] packScreen(Cursor c) {
+        Screen screen = new Screen();
+        screen.id = c.getLong(ID_INDEX);
+        screen.rank = c.getInt(SCREEN_RANK_INDEX);
+
+        return writeCheckedBytes(screen);
+    }
+
+    /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
+    private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Screen screen = new Screen();
+        MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
+        return screen;
+    }
+
+    /** Serialize an icon Resource for persistence, including a checksum wrapper. */
+    private byte[] packIcon(int dpi, Bitmap icon) {
+        Resource res = new Resource();
+        res.dpi = dpi;
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+            res.data = os.toByteArray();
+        }
+        return writeCheckedBytes(res);
+    }
+
+    /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
+    private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Resource res = new Resource();
+        MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
+        return res;
+    }
+
+    /** Serialize a widget for persistence, including a checksum wrapper. */
+    private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
+            ComponentName provider) {
+        final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
+        Widget widget = new Widget();
+        widget.provider = provider.flattenToShortString();
+        widget.label = info.label;
+        widget.configure = info.configure != null;
+        if (info.icon != 0) {
+            widget.icon = new Resource();
+            Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
+            Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+                widget.icon.data = os.toByteArray();
+                widget.icon.dpi = dpi;
+            }
+        }
+        if (info.previewImage != 0) {
+            widget.preview = new Resource();
+            Bitmap preview = previewLoader.generateWidgetPreview(info, null);
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+                widget.preview.data = os.toByteArray();
+                widget.preview.dpi = dpi;
+            }
+        }
+        return writeCheckedBytes(widget);
+    }
+
+    /** Deserialize a widget from persistence, after verifying checksum wrapper. */
+    private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        Widget widget = new Widget();
+        MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
+        return widget;
+    }
+
+    /**
+     * Read the old journal from the input file.
+     *
+     * In the event of any error, just pretend we didn't have a journal,
+     * in that case, do a full backup.
+     *
+     * @param oldState the read-0only file descriptor pointing to the old journal
+     * @return a Journal protocol bugffer
+     */
+    private Journal readJournal(ParcelFileDescriptor oldState) {
+        Journal journal = new Journal();
+        if (oldState == null) {
+            return journal;
+        }
+        FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
+        try {
+            int remaining = inStream.available();
+            if (DEBUG) Log.d(TAG, "available " + remaining);
+            if (remaining < MAX_JOURNAL_SIZE) {
+                byte[] buffer = new byte[remaining];
+                int bytesRead = 0;
+                while (remaining > 0) {
+                    try {
+                        int result = inStream.read(buffer, bytesRead, remaining);
+                        if (result > 0) {
+                            if (DEBUG) Log.d(TAG, "read some bytes: " + result);
+                            remaining -= result;
+                            bytesRead += result;
+                        } else {
+                            // stop reading ands see what there is to parse
+                            Log.w(TAG, "read error: " + result);
+                            remaining = 0;
+                        }
+                    } catch (IOException e) {
+                        Log.w(TAG, "failed to read the journal", e);
+                        buffer = null;
+                        remaining = 0;
+                    }
+                }
+                if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
+
+                if (buffer != null) {
+                    try {
+                        MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
+                    } catch (InvalidProtocolBufferNanoException e) {
+                        Log.d(TAG, "failed to read the journal", e);
+                        journal.clear();
+                    }
+                }
+            }
+        } catch (IOException e) {
+            Log.d(TAG, "failed to close the journal", e);
+        } finally {
+            try {
+                inStream.close();
+            } catch (IOException e) {
+                Log.d(TAG, "failed to close the journal", e);
+            }
+        }
+        return journal;
+    }
+
+    private void writeRowToBackup(Key key, byte[] blob, Journal out,
+            BackupDataOutput data) throws IOException {
+        String backupKey = keyToBackupKey(key);
+        data.writeEntityHeader(backupKey, blob.length);
+        data.writeEntityData(blob, blob.length);
+        out.rows++;
+        out.bytes += blob.length;
+        Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
+                getKeyName(key) + "/" + blob.length);
+        if(DEBUG_PAYLOAD) {
+            String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
+            final int chunkSize = 1024;
+            for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
+                int end = offset + chunkSize;
+                end = Math.min(end, encoded.length());
+                Log.d(TAG, "wrote " + encoded.substring(offset, end));
+            }
+        }
+    }
+
+    private Set<String> getSavedIdsByType(int type, Journal in) {
+        Set<String> savedIds = new HashSet<String>();
+        for(int i = 0; i < in.key.length; i++) {
+            Key key = in.key[i];
+            if (key.type == type) {
+                savedIds.add(keyToBackupKey(key));
+            }
+        }
+        return savedIds;
+    }
+
+    private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
+            throws IOException {
+        int rows = 0;
+        for(String deleted: deletedIds) {
+            Log.v(TAG, "dropping icon " + deleted);
+            data.writeEntityHeader(deleted, -1);
+            rows++;
+        }
+        return rows;
+    }
+
+    /**
+     * Write the new journal to the output file.
+     *
+     * In the event of any error, just pretend we didn't have a journal,
+     * in that case, do a full backup.
+
+     * @param newState the write-only file descriptor pointing to the new journal
+     * @param journal a Journal protocol buffer
+     */
+    private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
+        FileOutputStream outStream = null;
+        try {
+            outStream = new FileOutputStream(newState.getFileDescriptor());
+            outStream.write(writeCheckedBytes(journal));
+            outStream.close();
+        } catch (IOException e) {
+            Log.d(TAG, "failed to write backup journal", e);
+        }
+    }
+
+    /** Wrap a proto in a CheckedMessage and compute the checksum. */
+    private byte[] writeCheckedBytes(MessageNano proto) {
+        CheckedMessage wrapper = new CheckedMessage();
+        wrapper.payload = MessageNano.toByteArray(proto);
+        CRC32 checksum = new CRC32();
+        checksum.update(wrapper.payload);
+        wrapper.checksum = checksum.getValue();
+        return MessageNano.toByteArray(wrapper);
+    }
+
+    /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
+    private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+            throws InvalidProtocolBufferNanoException {
+        CheckedMessage wrapper = new CheckedMessage();
+        MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
+        CRC32 checksum = new CRC32();
+        checksum.update(wrapper.payload);
+        if (wrapper.checksum != checksum.getValue()) {
+            throw new InvalidProtocolBufferNanoException("checksum does not match");
+        }
+        return wrapper.payload;
+    }
+
+    private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
+        if (mWidgetMap == null) {
+            List<AppWidgetProviderInfo> widgets =
+                    AppWidgetManager.getInstance(mContext).getInstalledProviders();
+            mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
+            for (AppWidgetProviderInfo info : widgets) {
+                mWidgetMap.put(info.provider, info);
+            }
+        }
+        return mWidgetMap.get(component);
+    }
+
+    private class KeyParsingException extends Throwable {
+        private KeyParsingException(Throwable cause) {
+            super(cause);
+        }
+
+        public KeyParsingException(String reason) {
+            super(reason);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
new file mode 100644
index 0000000..7e1442d
--- /dev/null
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -0,0 +1,3182 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.*;
+import android.content.Intent.ShortcutIconResource;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.BaseColumns;
+import android.util.Log;
+import android.util.Pair;
+import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
+
+import java.lang.ref.WeakReference;
+import java.net.URISyntaxException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Maintains in-memory state of the Launcher. It is expected that there should be only one
+ * LauncherModel object held in a static. Also provide APIs for updating the database state
+ * for the Launcher.
+ */
+public class LauncherModel extends BroadcastReceiver {
+    static final boolean DEBUG_LOADERS = false;
+    static final String TAG = "Launcher.Model";
+
+    // true = use a "More Apps" folder for non-workspace apps on upgrade
+    // false = strew non-workspace apps across the workspace on upgrade
+    public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
+
+    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
+    private final boolean mAppsCanBeOnRemoveableStorage;
+
+    private final LauncherAppState mApp;
+    private final Object mLock = new Object();
+    private DeferredHandler mHandler = new DeferredHandler();
+    private LoaderTask mLoaderTask;
+    private boolean mIsLoaderTaskRunning;
+    private volatile boolean mFlushingWorkerThread;
+
+    // Specific runnable types that are run on the main thread deferred handler, this allows us to
+    // clear all queued binding runnables when the Launcher activity is destroyed.
+    private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
+    private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
+
+
+    private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
+    static {
+        sWorkerThread.start();
+    }
+    private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
+
+    // We start off with everything not loaded.  After that, we assume that
+    // our monitoring of the package manager provides all updates and we never
+    // need to do a requery.  These are only ever touched from the loader thread.
+    private boolean mWorkspaceLoaded;
+    private boolean mAllAppsLoaded;
+
+    // When we are loading pages synchronously, we can't just post the binding of items on the side
+    // pages as this delays the rotation process.  Instead, we wait for a callback from the first
+    // draw (in Workspace) to initiate the binding of the remaining side pages.  Any time we start
+    // a normal load, we also clear this set of Runnables.
+    static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
+
+    private WeakReference<Callbacks> mCallbacks;
+
+    // < only access in worker thread >
+    AllAppsList mBgAllAppsList;
+
+    // The lock that must be acquired before referencing any static bg data structures.  Unlike
+    // other locks, this one can generally be held long-term because we never expect any of these
+    // static data structures to be referenced outside of the worker thread except on the first
+    // load after configuration change.
+    static final Object sBgLock = new Object();
+
+    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
+    // LauncherModel to their ids
+    static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
+
+    // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
+    //       created by LauncherModel that are directly on the home screen (however, no widgets or
+    //       shortcuts within folders).
+    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
+
+    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
+    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
+        new ArrayList<LauncherAppWidgetInfo>();
+
+    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
+    static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
+
+    // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
+    static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
+
+    // sBgWorkspaceScreens is the ordered set of workspace screens.
+    static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
+
+    // </ only access in worker thread >
+
+    private IconCache mIconCache;
+    private Bitmap mDefaultIcon;
+
+    protected int mPreviousConfigMcc;
+
+    public interface Callbacks {
+        public boolean setLoadOnResume();
+        public int getCurrentWorkspaceScreen();
+        public void startBinding();
+        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
+                              boolean forceAnimateIcons);
+        public void bindScreens(ArrayList<Long> orderedScreenIds);
+        public void bindAddScreens(ArrayList<Long> orderedScreenIds);
+        public void bindFolders(HashMap<Long,FolderInfo> folders);
+        public void finishBindingItems(boolean upgradePath);
+        public void bindAppWidget(LauncherAppWidgetInfo info);
+        public void bindAllApplications(ArrayList<AppInfo> apps);
+        public void bindAppsAdded(ArrayList<Long> newScreens,
+                                  ArrayList<ItemInfo> addNotAnimated,
+                                  ArrayList<ItemInfo> addAnimated,
+                                  ArrayList<AppInfo> addedApps);
+        public void bindAppsUpdated(ArrayList<AppInfo> apps);
+        public void bindComponentsRemoved(ArrayList<String> packageNames,
+                        ArrayList<AppInfo> appInfos,
+                        boolean matchPackageNamesOnly);
+        public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
+        public void bindSearchablesChanged();
+        public boolean isAllAppsButtonRank(int rank);
+        public void onPageBoundSynchronously(int page);
+        public void dumpLogsToLocalData();
+    }
+
+    public interface ItemInfoFilter {
+        public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
+    }
+
+    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+        final Context context = app.getContext();
+
+        mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
+        mApp = app;
+        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
+        mIconCache = iconCache;
+
+        mDefaultIcon = Utilities.createIconBitmap(
+                mIconCache.getFullResDefaultActivityIcon(), context);
+
+        final Resources res = context.getResources();
+        Configuration config = res.getConfiguration();
+        mPreviousConfigMcc = config.mcc;
+    }
+
+    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
+     * posted on the main thread handler. */
+    private void runOnMainThread(Runnable r) {
+        runOnMainThread(r, 0);
+    }
+    private void runOnMainThread(Runnable r, int type) {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            // If we are on the worker thread, post onto the main handler
+            mHandler.post(r);
+        } else {
+            r.run();
+        }
+    }
+
+    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
+     * posted on the worker thread handler. */
+    private static void runOnWorkerThread(Runnable r) {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            r.run();
+        } else {
+            // If we are not on the worker thread, then post to the worker handler
+            sWorker.post(r);
+        }
+    }
+
+    static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
+                                 long screen) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        final int xCount = (int) grid.numColumns;
+        final int yCount = (int) grid.numRows;
+        boolean[][] occupied = new boolean[xCount][yCount];
+
+        int cellX, cellY, spanX, spanY;
+        for (int i = 0; i < items.size(); ++i) {
+            final ItemInfo item = items.get(i);
+            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (item.screenId == screen) {
+                    cellX = item.cellX;
+                    cellY = item.cellY;
+                    spanX = item.spanX;
+                    spanY = item.spanY;
+                    for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
+                        for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
+                            occupied[x][y] = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
+    }
+    static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
+                                                        Intent launchIntent,
+                                                        int firstScreenIndex,
+                                                        ArrayList<Long> workspaceScreens) {
+        // Lock on the app so that we don't try and get the items while apps are being added
+        LauncherAppState app = LauncherAppState.getInstance();
+        LauncherModel model = app.getModel();
+        boolean found = false;
+        synchronized (app) {
+            if (sWorkerThread.getThreadId() != Process.myTid()) {
+                // Flush the LauncherModel worker thread, so that if we just did another
+                // processInstallShortcut, we give it time for its shortcut to get added to the
+                // database (getItemsInLocalCoordinates reads the database)
+                model.flushWorkerThread();
+            }
+            final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+
+            // Try adding to the workspace screens incrementally, starting at the default or center
+            // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
+            firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size());
+            int count = workspaceScreens.size();
+            for (int screen = firstScreenIndex; screen < count && !found; screen++) {
+                int[] tmpCoordinates = new int[2];
+                if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
+                        workspaceScreens.get(screen))) {
+                    // Update the Launcher db
+                    return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates);
+                }
+            }
+        }
+        return null;
+    }
+
+    public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
+                                    final ArrayList<AppInfo> allAppsApps) {
+        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+        addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
+    }
+    public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
+                                    final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+        if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
+            return;
+        }
+        // Process the newly added applications and add them to the database first
+        Runnable r = new Runnable() {
+            public void run() {
+                final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
+                final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+
+                // Get the list of workspace screens.  We need to append to this list and
+                // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
+                // called.
+                ArrayList<Long> workspaceScreens = new ArrayList<Long>();
+                TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
+                for (Integer i : orderedScreens.keySet()) {
+                    long screenId = orderedScreens.get(i);
+                    workspaceScreens.add(screenId);
+                }
+
+                synchronized(sBgLock) {
+                    Iterator<ItemInfo> iter = workspaceApps.iterator();
+                    while (iter.hasNext()) {
+                        ItemInfo a = iter.next();
+                        final String name = a.title.toString();
+                        final Intent launchIntent = a.getIntent();
+
+                        // Short-circuit this logic if the icon exists somewhere on the workspace
+                        if (LauncherModel.shortcutExists(context, name, launchIntent)) {
+                            continue;
+                        }
+
+                        // Add this icon to the db, creating a new page if necessary.  If there
+                        // is only the empty page then we just add items to the first page.
+                        // Otherwise, we add them to the next pages.
+                        int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
+                        Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
+                                name, launchIntent, startSearchPageIndex, workspaceScreens);
+                        if (coords == null) {
+                            LauncherProvider lp = LauncherAppState.getLauncherProvider();
+
+                            // If we can't find a valid position, then just add a new screen.
+                            // This takes time so we need to re-queue the add until the new
+                            // page is added.  Create as many screens as necessary to satisfy
+                            // the startSearchPageIndex.
+                            int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
+                                    workspaceScreens.size());
+                            while (numPagesToAdd > 0) {
+                                long screenId = lp.generateNewScreenId();
+                                // Save the screen id for binding in the workspace
+                                workspaceScreens.add(screenId);
+                                addedWorkspaceScreensFinal.add(screenId);
+                                numPagesToAdd--;
+                            }
+
+                            // Find the coordinate again
+                            coords = LauncherModel.findNextAvailableIconSpace(context,
+                                    name, launchIntent, startSearchPageIndex, workspaceScreens);
+                        }
+                        if (coords == null) {
+                            throw new RuntimeException("Coordinates should not be null");
+                        }
+
+                        ShortcutInfo shortcutInfo;
+                        if (a instanceof ShortcutInfo) {
+                            shortcutInfo = (ShortcutInfo) a;
+                        } else if (a instanceof AppInfo) {
+                            shortcutInfo = ((AppInfo) a).makeShortcut();
+                        } else {
+                            throw new RuntimeException("Unexpected info type");
+                        }
+
+                        // Add the shortcut to the db
+                        addItemToDatabase(context, shortcutInfo,
+                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                                coords.first, coords.second[0], coords.second[1], false);
+                        // Save the ShortcutInfo for binding in the workspace
+                        addedShortcutsFinal.add(shortcutInfo);
+                    }
+                }
+
+                // Update the workspace screens
+                updateWorkspaceScreenOrder(context, workspaceScreens);
+
+                if (!addedShortcutsFinal.isEmpty() || !allAppsApps.isEmpty()) {
+                    runOnMainThread(new Runnable() {
+                        public void run() {
+                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            if (callbacks == cb && cb != null) {
+                                final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+                                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+                                if (!addedShortcutsFinal.isEmpty()) {
+                                    ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                                    long lastScreenId = info.screenId;
+                                    for (ItemInfo i : addedShortcutsFinal) {
+                                        if (i.screenId == lastScreenId) {
+                                            addAnimated.add(i);
+                                        } else {
+                                            addNotAnimated.add(i);
+                                        }
+                                    }
+                                }
+                                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+                                        addNotAnimated, addAnimated, allAppsApps);
+                            }
+                        }
+                    });
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    public Bitmap getFallbackIcon() {
+        return Bitmap.createBitmap(mDefaultIcon);
+    }
+
+    public void unbindItemInfosAndClearQueuedBindRunnables() {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
+                    "main thread");
+        }
+
+        // Clear any deferred bind runnables
+        mDeferredBindRunnables.clear();
+        // Remove any queued bind runnables
+        mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
+        // Unbind all the workspace items
+        unbindWorkspaceItemsOnMainThread();
+    }
+
+    /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
+    void unbindWorkspaceItemsOnMainThread() {
+        // Ensure that we don't use the same workspace items data structure on the main thread
+        // by making a copy of workspace items first.
+        final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
+        final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
+        synchronized (sBgLock) {
+            tmpWorkspaceItems.addAll(sBgWorkspaceItems);
+            tmpAppWidgets.addAll(sBgAppWidgets);
+        }
+        Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                   for (ItemInfo item : tmpWorkspaceItems) {
+                       item.unbind();
+                   }
+                   for (ItemInfo item : tmpAppWidgets) {
+                       item.unbind();
+                   }
+                }
+            };
+        runOnMainThread(r);
+    }
+
+    /**
+     * Adds an item to the DB if it was not created previously, or move it to a new
+     * <container, screen, cellX, cellY>
+     */
+    static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
+            long screenId, int cellX, int cellY) {
+        if (item.container == ItemInfo.NO_ID) {
+            // From all apps
+            addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
+        } else {
+            // From somewhere else
+            moveItemInDatabase(context, item, container, screenId, cellX, cellY);
+        }
+    }
+
+    static void checkItemInfoLocked(
+            final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
+        ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+        if (modelItem != null && item != modelItem) {
+            // check all the data is consistent
+            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
+                ShortcutInfo shortcut = (ShortcutInfo) item;
+                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
+                        modelShortcut.intent.filterEquals(shortcut.intent) &&
+                        modelShortcut.id == shortcut.id &&
+                        modelShortcut.itemType == shortcut.itemType &&
+                        modelShortcut.container == shortcut.container &&
+                        modelShortcut.screenId == shortcut.screenId &&
+                        modelShortcut.cellX == shortcut.cellX &&
+                        modelShortcut.cellY == shortcut.cellY &&
+                        modelShortcut.spanX == shortcut.spanX &&
+                        modelShortcut.spanY == shortcut.spanY &&
+                        ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
+                        (modelShortcut.dropPos != null &&
+                                shortcut.dropPos != null &&
+                                modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
+                        modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
+                    // For all intents and purposes, this is the same object
+                    return;
+                }
+            }
+
+            // the modelItem needs to match up perfectly with item if our model is
+            // to be consistent with the database-- for now, just require
+            // modelItem == item or the equality check above
+            String msg = "item: " + ((item != null) ? item.toString() : "null") +
+                    "modelItem: " +
+                    ((modelItem != null) ? modelItem.toString() : "null") +
+                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
+            RuntimeException e = new RuntimeException(msg);
+            if (stackTrace != null) {
+                e.setStackTrace(stackTrace);
+            }
+            // TODO: something breaks this in the upgrade path
+            //throw e;
+        }
+    }
+
+    static void checkItemInfo(final ItemInfo item) {
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        final long itemId = item.id;
+        Runnable r = new Runnable() {
+            public void run() {
+                synchronized (sBgLock) {
+                    checkItemInfoLocked(itemId, item, stackTrace);
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+            final ItemInfo item, final String callingFunction) {
+        final long itemId = item.id;
+        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
+        final ContentResolver cr = context.getContentResolver();
+
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        Runnable r = new Runnable() {
+            public void run() {
+                cr.update(uri, values, null, null);
+                updateItemArrays(item, itemId, stackTrace);
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
+            final ArrayList<ItemInfo> items, final String callingFunction) {
+        final ContentResolver cr = context.getContentResolver();
+
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        Runnable r = new Runnable() {
+            public void run() {
+                ArrayList<ContentProviderOperation> ops =
+                        new ArrayList<ContentProviderOperation>();
+                int count = items.size();
+                for (int i = 0; i < count; i++) {
+                    ItemInfo item = items.get(i);
+                    final long itemId = item.id;
+                    final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
+                    ContentValues values = valuesList.get(i);
+
+                    ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+                    updateItemArrays(item, itemId, stackTrace);
+
+                }
+                try {
+                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
+        // Lock on mBgLock *after* the db operation
+        synchronized (sBgLock) {
+            checkItemInfoLocked(itemId, item, stackTrace);
+
+            if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                    item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                // Item is in a folder, make sure this folder exists
+                if (!sBgFolders.containsKey(item.container)) {
+                    // An items container is being set to a that of an item which is not in
+                    // the list of Folders.
+                    String msg = "item: " + item + " container being set to: " +
+                            item.container + ", not in the list of folders";
+                    Log.e(TAG, msg);
+                }
+            }
+
+            // Items are added/removed from the corresponding FolderInfo elsewhere, such
+            // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+            // that are on the desktop, as appropriate
+            ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+            if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                    modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                switch (modelItem.itemType) {
+                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                        if (!sBgWorkspaceItems.contains(modelItem)) {
+                            sBgWorkspaceItems.add(modelItem);
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            } else {
+                sBgWorkspaceItems.remove(modelItem);
+            }
+        }
+    }
+
+    public void flushWorkerThread() {
+        mFlushingWorkerThread = true;
+        Runnable waiter = new Runnable() {
+                public void run() {
+                    synchronized (this) {
+                        notifyAll();
+                        mFlushingWorkerThread = false;
+                    }
+                }
+            };
+
+        synchronized(waiter) {
+            runOnWorkerThread(waiter);
+            if (mLoaderTask != null) {
+                synchronized(mLoaderTask) {
+                    mLoaderTask.notify();
+                }
+            }
+            boolean success = false;
+            while (!success) {
+                try {
+                    waiter.wait();
+                    success = true;
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Move an item in the DB to a new <container, screen, cellX, cellY>
+     */
+    static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
+            final long screenId, final int cellX, final int cellY) {
+        item.container = container;
+        item.cellX = cellX;
+        item.cellY = cellY;
+
+        // We store hotseat items in canonical form which is this orientation invariant position
+        // in the hotseat
+        if (context instanceof Launcher && screenId < 0 &&
+                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+        } else {
+            item.screenId = screenId;
+        }
+
+        final ContentValues values = new ContentValues();
+        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+    }
+
+    /**
+     * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
+     * cellX, cellY have already been updated on the ItemInfos.
+     */
+    static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
+            final long container, final int screen) {
+
+        ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
+        int count = items.size();
+
+        for (int i = 0; i < count; i++) {
+            ItemInfo item = items.get(i);
+            item.container = container;
+
+            // We store hotseat items in canonical form which is this orientation invariant position
+            // in the hotseat
+            if (context instanceof Launcher && screen < 0 &&
+                    container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
+                        item.cellY);
+            } else {
+                item.screenId = screen;
+            }
+
+            final ContentValues values = new ContentValues();
+            values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+            values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+            values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+            values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+            contentValues.add(values);
+        }
+        updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
+    }
+
+    /**
+     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
+     */
+    static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
+            final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
+        item.container = container;
+        item.cellX = cellX;
+        item.cellY = cellY;
+        item.spanX = spanX;
+        item.spanY = spanY;
+
+        // We store hotseat items in canonical form which is this orientation invariant position
+        // in the hotseat
+        if (context instanceof Launcher && screenId < 0 &&
+                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+        } else {
+            item.screenId = screenId;
+        }
+
+        final ContentValues values = new ContentValues();
+        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
+        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
+        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+    }
+
+    /**
+     * Update an item to the database in a specified container.
+     */
+    static void updateItemInDatabase(Context context, final ItemInfo item) {
+        final ContentValues values = new ContentValues();
+        item.onAddToDatabase(values);
+        item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
+        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
+    }
+
+    /**
+     * Returns true if the shortcuts already exists in the database.
+     * we identify a shortcut by its title and intent.
+     */
+    static boolean shortcutExists(Context context, String title, Intent intent) {
+        final ContentResolver cr = context.getContentResolver();
+        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
+            new String[] { "title", "intent" }, "title=? and intent=?",
+            new String[] { title, intent.toUri(0) }, null);
+        boolean result = false;
+        try {
+            result = c.moveToFirst();
+        } finally {
+            c.close();
+        }
+        return result;
+    }
+
+    /**
+     * Returns an ItemInfo array containing all the items in the LauncherModel.
+     * The ItemInfo.id is not set through this function.
+     */
+    static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
+        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+        final ContentResolver cr = context.getContentResolver();
+        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
+                LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
+                LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+                LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
+
+        final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+        final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+        final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+        final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+        final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+        final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+        final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+
+        try {
+            while (c.moveToNext()) {
+                ItemInfo item = new ItemInfo();
+                item.cellX = c.getInt(cellXIndex);
+                item.cellY = c.getInt(cellYIndex);
+                item.spanX = Math.max(1, c.getInt(spanXIndex));
+                item.spanY = Math.max(1, c.getInt(spanYIndex));
+                item.container = c.getInt(containerIndex);
+                item.itemType = c.getInt(itemTypeIndex);
+                item.screenId = c.getInt(screenIndex);
+
+                items.add(item);
+            }
+        } catch (Exception e) {
+            items.clear();
+        } finally {
+            c.close();
+        }
+
+        return items;
+    }
+
+    /**
+     * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
+     */
+    FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
+        final ContentResolver cr = context.getContentResolver();
+        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
+                "_id=? and (itemType=? or itemType=?)",
+                new String[] { String.valueOf(id),
+                        String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
+
+        try {
+            if (c.moveToFirst()) {
+                final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+                final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+                final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+                final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+                final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+                final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+
+                FolderInfo folderInfo = null;
+                switch (c.getInt(itemTypeIndex)) {
+                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                        folderInfo = findOrMakeFolder(folderList, id);
+                        break;
+                }
+
+                folderInfo.title = c.getString(titleIndex);
+                folderInfo.id = id;
+                folderInfo.container = c.getInt(containerIndex);
+                folderInfo.screenId = c.getInt(screenIndex);
+                folderInfo.cellX = c.getInt(cellXIndex);
+                folderInfo.cellY = c.getInt(cellYIndex);
+
+                return folderInfo;
+            }
+        } finally {
+            c.close();
+        }
+
+        return null;
+    }
+
+    /**
+     * Add an item to the database in a specified container. Sets the container, screen, cellX and
+     * cellY fields of the item. Also assigns an ID to the item.
+     */
+    static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+            final long screenId, final int cellX, final int cellY, final boolean notify) {
+        item.container = container;
+        item.cellX = cellX;
+        item.cellY = cellY;
+        // We store hotseat items in canonical form which is this orientation invariant position
+        // in the hotseat
+        if (context instanceof Launcher && screenId < 0 &&
+                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+        } else {
+            item.screenId = screenId;
+        }
+
+        final ContentValues values = new ContentValues();
+        final ContentResolver cr = context.getContentResolver();
+        item.onAddToDatabase(values);
+
+        item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
+        values.put(LauncherSettings.Favorites._ID, item.id);
+        item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
+
+        Runnable r = new Runnable() {
+            public void run() {
+                cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
+                        LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
+
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    checkItemInfoLocked(item.id, item, null);
+                    sBgItemsIdMap.put(item.id, item);
+                    switch (item.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            sBgFolders.put(item.id, (FolderInfo) item);
+                            // Fall through
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                                    item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                                sBgWorkspaceItems.add(item);
+                            } else {
+                                if (!sBgFolders.containsKey(item.container)) {
+                                    // Adding an item to a folder that doesn't exist.
+                                    String msg = "adding item: " + item + " to a folder that " +
+                                            " doesn't exist";
+                                    Log.e(TAG, msg);
+                                }
+                            }
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                            sBgAppWidgets.add((LauncherAppWidgetInfo) item);
+                            break;
+                    }
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    /**
+     * Creates a new unique child id, for a given cell span across all layouts.
+     */
+    static int getCellLayoutChildId(
+            long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
+        return (((int) container & 0xFF) << 24)
+                | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
+    }
+
+    /**
+     * Removes the specified item from the database
+     * @param context
+     * @param item
+     */
+    static void deleteItemFromDatabase(Context context, final ItemInfo item) {
+        final ContentResolver cr = context.getContentResolver();
+        final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
+
+        Runnable r = new Runnable() {
+            public void run() {
+                cr.delete(uriToDelete, null, null);
+
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    switch (item.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            sBgFolders.remove(item.id);
+                            for (ItemInfo info: sBgItemsIdMap.values()) {
+                                if (info.container == item.id) {
+                                    // We are deleting a folder which still contains items that
+                                    // think they are contained by that folder.
+                                    String msg = "deleting a folder (" + item + ") which still " +
+                                            "contains items (" + info + ")";
+                                    Log.e(TAG, msg);
+                                }
+                            }
+                            sBgWorkspaceItems.remove(item);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            sBgWorkspaceItems.remove(item);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                            sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
+                            break;
+                    }
+                    sBgItemsIdMap.remove(item.id);
+                    sBgDbIconCache.remove(item);
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    /**
+     * Update the order of the workspace screens in the database. The array list contains
+     * a list of screen ids in the order that they should appear.
+     */
+    void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+        final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
+        final ContentResolver cr = context.getContentResolver();
+        final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+
+        // Remove any negative screen ids -- these aren't persisted
+        Iterator<Long> iter = screensCopy.iterator();
+        while (iter.hasNext()) {
+            long id = iter.next();
+            if (id < 0) {
+                iter.remove();
+            }
+        }
+
+        Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                // Clear the table
+                cr.delete(uri, null, null);
+                int count = screensCopy.size();
+                ContentValues[] values = new ContentValues[count];
+                for (int i = 0; i < count; i++) {
+                    ContentValues v = new ContentValues();
+                    long screenId = screensCopy.get(i);
+                    v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+                    v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+                    values[i] = v;
+                }
+                cr.bulkInsert(uri, values);
+
+                synchronized (sBgLock) {
+                    sBgWorkspaceScreens.clear();
+                    sBgWorkspaceScreens.addAll(screensCopy);
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    /**
+     * Remove the contents of the specified folder from the database
+     */
+    static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
+        final ContentResolver cr = context.getContentResolver();
+
+        Runnable r = new Runnable() {
+            public void run() {
+                cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    sBgItemsIdMap.remove(info.id);
+                    sBgFolders.remove(info.id);
+                    sBgDbIconCache.remove(info);
+                    sBgWorkspaceItems.remove(info);
+                }
+
+                cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
+                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+                // Lock on mBgLock *after* the db operation
+                synchronized (sBgLock) {
+                    for (ItemInfo childInfo : info.contents) {
+                        sBgItemsIdMap.remove(childInfo.id);
+                        sBgDbIconCache.remove(childInfo);
+                    }
+                }
+            }
+        };
+        runOnWorkerThread(r);
+    }
+
+    /**
+     * Set this as the current Launcher activity object for the loader.
+     */
+    public void initialize(Callbacks callbacks) {
+        synchronized (mLock) {
+            mCallbacks = new WeakReference<Callbacks>(callbacks);
+        }
+    }
+
+    /**
+     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
+     * ACTION_PACKAGE_CHANGED.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
+
+        final String action = intent.getAction();
+
+        if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+                || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+            final String packageName = intent.getData().getSchemeSpecificPart();
+            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+            int op = PackageUpdatedTask.OP_NONE;
+
+            if (packageName == null || packageName.length() == 0) {
+                // they sent us a bad intent
+                return;
+            }
+
+            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                op = PackageUpdatedTask.OP_UPDATE;
+            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                if (!replacing) {
+                    op = PackageUpdatedTask.OP_REMOVE;
+                }
+                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
+                // later, we will update the package at this time
+            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                if (!replacing) {
+                    op = PackageUpdatedTask.OP_ADD;
+                } else {
+                    op = PackageUpdatedTask.OP_UPDATE;
+                }
+            }
+
+            if (op != PackageUpdatedTask.OP_NONE) {
+                enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
+            }
+
+        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+            // First, schedule to add these apps back in.
+            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
+            // Then, rebind everything.
+            startLoaderFromBackground();
+        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            enqueuePackageUpdated(new PackageUpdatedTask(
+                        PackageUpdatedTask.OP_UNAVAILABLE, packages));
+        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+            // If we have changed locale we need to clear out the labels in all apps/workspace.
+            forceReload();
+        } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+             // Check if configuration change was an mcc/mnc change which would affect app resources
+             // and we would need to clear out the labels in all apps/workspace. Same handling as
+             // above for ACTION_LOCALE_CHANGED
+             Configuration currentConfig = context.getResources().getConfiguration();
+             if (mPreviousConfigMcc != currentConfig.mcc) {
+                   Log.d(TAG, "Reload apps on config change. curr_mcc:"
+                       + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
+                   forceReload();
+             }
+             // Update previousConfig
+             mPreviousConfigMcc = currentConfig.mcc;
+        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
+                   SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+            if (mCallbacks != null) {
+                Callbacks callbacks = mCallbacks.get();
+                if (callbacks != null) {
+                    callbacks.bindSearchablesChanged();
+                }
+            }
+        }
+    }
+
+    private void forceReload() {
+        resetLoadedState(true, true);
+
+        // Do this here because if the launcher activity is running it will be restarted.
+        // If it's not running startLoaderFromBackground will merely tell it that it needs
+        // to reload.
+        startLoaderFromBackground();
+    }
+
+    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
+        synchronized (mLock) {
+            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
+            // mWorkspaceLoaded to true later
+            stopLoaderLocked();
+            if (resetAllAppsLoaded) mAllAppsLoaded = false;
+            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+        }
+    }
+
+    /**
+     * When the launcher is in the background, it's possible for it to miss paired
+     * configuration changes.  So whenever we trigger the loader from the background
+     * tell the launcher that it needs to re-run the loader when it comes back instead
+     * of doing it now.
+     */
+    public void startLoaderFromBackground() {
+        boolean runLoader = false;
+        if (mCallbacks != null) {
+            Callbacks callbacks = mCallbacks.get();
+            if (callbacks != null) {
+                // Only actually run the loader if they're not paused.
+                if (!callbacks.setLoadOnResume()) {
+                    runLoader = true;
+                }
+            }
+        }
+        if (runLoader) {
+            startLoader(false, -1);
+        }
+    }
+
+    // If there is already a loader task running, tell it to stop.
+    // returns true if isLaunching() was true on the old task
+    private boolean stopLoaderLocked() {
+        boolean isLaunching = false;
+        LoaderTask oldTask = mLoaderTask;
+        if (oldTask != null) {
+            if (oldTask.isLaunching()) {
+                isLaunching = true;
+            }
+            oldTask.stopLocked();
+        }
+        return isLaunching;
+    }
+
+    public void startLoader(boolean isLaunching, int synchronousBindPage) {
+        synchronized (mLock) {
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
+            }
+
+            // Clear any deferred bind-runnables from the synchronized load process
+            // We must do this before any loading/binding is scheduled below.
+            mDeferredBindRunnables.clear();
+
+            // Don't bother to start the thread if we know it's not going to do anything
+            if (mCallbacks != null && mCallbacks.get() != null) {
+                // If there is already one running, tell it to stop.
+                // also, don't downgrade isLaunching if we're already running
+                isLaunching = isLaunching || stopLoaderLocked();
+                mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching);
+                if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
+                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
+                } else {
+                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
+                    sWorker.post(mLoaderTask);
+                }
+            }
+        }
+    }
+
+    void bindRemainingSynchronousPages() {
+        // Post the remaining side pages to be loaded
+        if (!mDeferredBindRunnables.isEmpty()) {
+            for (final Runnable r : mDeferredBindRunnables) {
+                mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
+            }
+            mDeferredBindRunnables.clear();
+        }
+    }
+
+    public void stopLoader() {
+        synchronized (mLock) {
+            if (mLoaderTask != null) {
+                mLoaderTask.stopLocked();
+            }
+        }
+    }
+
+    /** Loads the workspace screens db into a map of Rank -> ScreenId */
+    private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
+        final ContentResolver contentResolver = context.getContentResolver();
+        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+        final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
+        TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
+
+        try {
+            final int idIndex = sc.getColumnIndexOrThrow(
+                    LauncherSettings.WorkspaceScreens._ID);
+            final int rankIndex = sc.getColumnIndexOrThrow(
+                    LauncherSettings.WorkspaceScreens.SCREEN_RANK);
+            while (sc.moveToNext()) {
+                try {
+                    long screenId = sc.getLong(idIndex);
+                    int rank = sc.getInt(rankIndex);
+                    orderedScreens.put(rank, screenId);
+                } catch (Exception e) {
+                    Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e, true);
+                }
+            }
+        } finally {
+            sc.close();
+        }
+        return orderedScreens;
+    }
+
+    public boolean isAllAppsLoaded() {
+        return mAllAppsLoaded;
+    }
+
+    boolean isLoadingWorkspace() {
+        synchronized (mLock) {
+            if (mLoaderTask != null) {
+                return mLoaderTask.isLoadingWorkspace();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Runnable for the thread that loads the contents of the launcher:
+     *   - workspace icons
+     *   - widgets
+     *   - all apps icons
+     */
+    private class LoaderTask implements Runnable {
+        private Context mContext;
+        private boolean mIsLaunching;
+        private boolean mIsLoadingAndBindingWorkspace;
+        private boolean mStopped;
+        private boolean mLoadAndBindStepFinished;
+
+        private HashMap<Object, CharSequence> mLabelCache;
+
+        LoaderTask(Context context, boolean isLaunching) {
+            mContext = context;
+            mIsLaunching = isLaunching;
+            mLabelCache = new HashMap<Object, CharSequence>();
+        }
+
+        boolean isLaunching() {
+            return mIsLaunching;
+        }
+
+        boolean isLoadingWorkspace() {
+            return mIsLoadingAndBindingWorkspace;
+        }
+
+        /** Returns whether this is an upgrade path */
+        private boolean loadAndBindWorkspace() {
+            mIsLoadingAndBindingWorkspace = true;
+
+            // Load the workspace
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
+            }
+
+            boolean isUpgradePath = false;
+            if (!mWorkspaceLoaded) {
+                isUpgradePath = loadWorkspace();
+                synchronized (LoaderTask.this) {
+                    if (mStopped) {
+                        return isUpgradePath;
+                    }
+                    mWorkspaceLoaded = true;
+                }
+            }
+
+            // Bind the workspace
+            bindWorkspace(-1, isUpgradePath);
+            return isUpgradePath;
+        }
+
+        private void waitForIdle() {
+            // Wait until the either we're stopped or the other threads are done.
+            // This way we don't start loading all apps until the workspace has settled
+            // down.
+            synchronized (LoaderTask.this) {
+                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+                mHandler.postIdle(new Runnable() {
+                        public void run() {
+                            synchronized (LoaderTask.this) {
+                                mLoadAndBindStepFinished = true;
+                                if (DEBUG_LOADERS) {
+                                    Log.d(TAG, "done with previous binding step");
+                                }
+                                LoaderTask.this.notify();
+                            }
+                        }
+                    });
+
+                while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
+                    try {
+                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
+                        // wait no longer than 1sec at a time
+                        this.wait(1000);
+                    } catch (InterruptedException ex) {
+                        // Ignore
+                    }
+                }
+                if (DEBUG_LOADERS) {
+                    Log.d(TAG, "waited "
+                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
+                            + "ms for previous step to finish binding");
+                }
+            }
+        }
+
+        void runBindSynchronousPage(int synchronousBindPage) {
+            if (synchronousBindPage < 0) {
+                // Ensure that we have a valid page index to load synchronously
+                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
+                        "valid page index");
+            }
+            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+                // Ensure that we don't try and bind a specified page when the pages have not been
+                // loaded already (we should load everything asynchronously in that case)
+                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
+            }
+            synchronized (mLock) {
+                if (mIsLoaderTaskRunning) {
+                    // Ensure that we are never running the background loading at this point since
+                    // we also touch the background collections
+                    throw new RuntimeException("Error! Background loading is already running");
+                }
+            }
+
+            // XXX: Throw an exception if we are already loading (since we touch the worker thread
+            //      data structures, we can't allow any other thread to touch that data, but because
+            //      this call is synchronous, we can get away with not locking).
+
+            // The LauncherModel is static in the LauncherAppState and mHandler may have queued
+            // operations from the previous activity.  We need to ensure that all queued operations
+            // are executed before any synchronous binding work is done.
+            mHandler.flush();
+
+            // Divide the set of loaded items into those that we are binding synchronously, and
+            // everything else that is to be bound normally (asynchronously).
+            bindWorkspace(synchronousBindPage, false);
+            // XXX: For now, continue posting the binding of AllApps as there are other issues that
+            //      arise from that.
+            onlyBindAllApps();
+        }
+
+        public void run() {
+            boolean isUpgrade = false;
+
+            synchronized (mLock) {
+                mIsLoaderTaskRunning = true;
+            }
+            // Optimize for end-user experience: if the Launcher is up and // running with the
+            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
+            // workspace first (default).
+            keep_running: {
+                // Elevate priority when Home launches for the first time to avoid
+                // starving at boot time. Staring at a blank home is not cool.
+                synchronized (mLock) {
+                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
+                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
+                    android.os.Process.setThreadPriority(mIsLaunching
+                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
+                }
+                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
+                isUpgrade = loadAndBindWorkspace();
+
+                if (mStopped) {
+                    break keep_running;
+                }
+
+                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
+                // settled down.
+                synchronized (mLock) {
+                    if (mIsLaunching) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
+                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    }
+                }
+                waitForIdle();
+
+                // second step
+                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
+                loadAndBindAllApps();
+
+                // Restore the default thread priority after we are done loading items
+                synchronized (mLock) {
+                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                }
+            }
+
+            // Update the saved icons if necessary
+            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
+            synchronized (sBgLock) {
+                for (Object key : sBgDbIconCache.keySet()) {
+                    updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
+                }
+                sBgDbIconCache.clear();
+            }
+
+            if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+                // Ensure that all the applications that are in the system are
+                // represented on the home screen.
+                if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
+                    verifyApplications();
+                }
+            }
+
+            // Clear out this reference, otherwise we end up holding it until all of the
+            // callback runnables are done.
+            mContext = null;
+
+            synchronized (mLock) {
+                // If we are still the last one to be scheduled, remove ourselves.
+                if (mLoaderTask == this) {
+                    mLoaderTask = null;
+                }
+                mIsLoaderTaskRunning = false;
+            }
+        }
+
+        public void stopLocked() {
+            synchronized (LoaderTask.this) {
+                mStopped = true;
+                this.notify();
+            }
+        }
+
+        /**
+         * Gets the callbacks object.  If we've been stopped, or if the launcher object
+         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
+         * object that was around when the deferred message was scheduled, and if there's
+         * a new Callbacks object around then also return null.  This will save us from
+         * calling onto it with data that will be ignored.
+         */
+        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
+            synchronized (mLock) {
+                if (mStopped) {
+                    return null;
+                }
+
+                if (mCallbacks == null) {
+                    return null;
+                }
+
+                final Callbacks callbacks = mCallbacks.get();
+                if (callbacks != oldCallbacks) {
+                    return null;
+                }
+                if (callbacks == null) {
+                    Log.w(TAG, "no mCallbacks");
+                    return null;
+                }
+
+                return callbacks;
+            }
+        }
+
+        private void verifyApplications() {
+            final Context context = mApp.getContext();
+
+            // Cross reference all the applications in our apps list with items in the workspace
+            ArrayList<ItemInfo> tmpInfos;
+            ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
+            synchronized (sBgLock) {
+                for (AppInfo app : mBgAllAppsList.data) {
+                    tmpInfos = getItemInfoForComponentName(app.componentName);
+                    if (tmpInfos.isEmpty()) {
+                        // We are missing an application icon, so add this to the workspace
+                        added.add(app);
+                        // This is a rare event, so lets log it
+                        Log.e(TAG, "Missing Application on load: " + app);
+                    }
+                }
+            }
+            if (!added.isEmpty()) {
+                Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                addAndBindAddedApps(context, added, cb, null);
+            }
+        }
+
+        private boolean checkItemDimensions(ItemInfo info) {
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            return (info.cellX + info.spanX) > (int) grid.numColumns ||
+                    (info.cellY + info.spanY) > (int) grid.numRows;
+        }
+
+        // check & update map of what's occupied; used to discard overlapping/invalid items
+        private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
+                                           AtomicBoolean deleteOnItemOverlap) {
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            int countX = (int) grid.numColumns;
+            int countY = (int) grid.numRows;
+
+            long containerIndex = item.screenId;
+            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                // Return early if we detect that an item is under the hotseat button
+                if (mCallbacks == null ||
+                        mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
+                    deleteOnItemOverlap.set(true);
+                    return false;
+                }
+
+                if (occupied.containsKey(LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
+                    if (occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+                            [(int) item.screenId][0] != null) {
+                        Log.e(TAG, "Error loading shortcut into hotseat " + item
+                                + " into position (" + item.screenId + ":" + item.cellX + ","
+                                + item.cellY + ") occupied by "
+                                + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+                                [(int) item.screenId][0]);
+                            return false;
+                    }
+                } else {
+                    ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
+                    items[(int) item.screenId][0] = item;
+                    occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
+                    return true;
+                }
+            } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                // Skip further checking if it is not the hotseat or workspace container
+                return true;
+            }
+
+            if (!occupied.containsKey(item.screenId)) {
+                ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
+                occupied.put(item.screenId, items);
+            }
+
+            ItemInfo[][] screens = occupied.get(item.screenId);
+            // Check if any workspace icons overlap with each other
+            for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+                for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+                    if (screens[x][y] != null) {
+                        Log.e(TAG, "Error loading shortcut " + item
+                            + " into cell (" + containerIndex + "-" + item.screenId + ":"
+                            + x + "," + y
+                            + ") occupied by "
+                            + screens[x][y]);
+                        return false;
+                    }
+                }
+            }
+            for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+                for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+                    screens[x][y] = item;
+                }
+            }
+
+            return true;
+        }
+
+        /** Clears all the sBg data structures */
+        private void clearSBgDataStructures() {
+            synchronized (sBgLock) {
+                sBgWorkspaceItems.clear();
+                sBgAppWidgets.clear();
+                sBgFolders.clear();
+                sBgItemsIdMap.clear();
+                sBgDbIconCache.clear();
+                sBgWorkspaceScreens.clear();
+            }
+        }
+
+        /** Returns whether this is an upgradge path */
+        private boolean loadWorkspace() {
+            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+            final Context context = mContext;
+            final ContentResolver contentResolver = context.getContentResolver();
+            final PackageManager manager = context.getPackageManager();
+            final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
+            final boolean isSafeMode = manager.isSafeMode();
+
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            int countX = (int) grid.numColumns;
+            int countY = (int) grid.numRows;
+
+            // Make sure the default workspace is loaded, if needed
+            LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
+
+            // Check if we need to do any upgrade-path logic
+            boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+
+            synchronized (sBgLock) {
+                clearSBgDataStructures();
+
+                final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
+                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
+                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
+                final Cursor c = contentResolver.query(contentUri, null, null, null, null);
+
+                // +1 for the hotseat (it can be larger than the workspace)
+                // Load workspace in reverse order to ensure that latest items are loaded first (and
+                // before any earlier duplicates)
+                final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
+
+                try {
+                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+                    final int intentIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.INTENT);
+                    final int titleIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.TITLE);
+                    final int iconTypeIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_TYPE);
+                    final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+                    final int iconPackageIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_PACKAGE);
+                    final int iconResourceIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ICON_RESOURCE);
+                    final int containerIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.CONTAINER);
+                    final int itemTypeIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.ITEM_TYPE);
+                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.APPWIDGET_ID);
+                    final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.APPWIDGET_PROVIDER);
+                    final int screenIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.SCREEN);
+                    final int cellXIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.CELLX);
+                    final int cellYIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.CELLY);
+                    final int spanXIndex = c.getColumnIndexOrThrow
+                            (LauncherSettings.Favorites.SPANX);
+                    final int spanYIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.SPANY);
+                    //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
+                    //final int displayModeIndex = c.getColumnIndexOrThrow(
+                    //        LauncherSettings.Favorites.DISPLAY_MODE);
+
+                    ShortcutInfo info;
+                    String intentDescription;
+                    LauncherAppWidgetInfo appWidgetInfo;
+                    int container;
+                    long id;
+                    Intent intent;
+
+                    while (!mStopped && c.moveToNext()) {
+                        AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
+                        try {
+                            int itemType = c.getInt(itemTypeIndex);
+
+                            switch (itemType) {
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                                id = c.getLong(idIndex);
+                                intentDescription = c.getString(intentIndex);
+                                try {
+                                    intent = Intent.parseUri(intentDescription, 0);
+                                    ComponentName cn = intent.getComponent();
+                                    if (cn != null && !isValidPackageComponent(manager, cn)) {
+                                        if (!mAppsCanBeOnRemoveableStorage) {
+                                            // Log the invalid package, and remove it from the db
+                                            Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
+                                            itemsToRemove.add(id);
+                                        } else {
+                                            // If apps can be on external storage, then we just
+                                            // leave them for the user to remove (maybe add
+                                            // visual treatment to it)
+                                            Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true);
+                                        }
+                                        continue;
+                                    }
+                                } catch (URISyntaxException e) {
+                                    Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true);
+                                    continue;
+                                }
+
+                                if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
+                                            titleIndex, mLabelCache);
+                                } else {
+                                    info = getShortcutInfo(c, context, iconTypeIndex,
+                                            iconPackageIndex, iconResourceIndex, iconIndex,
+                                            titleIndex);
+
+                                    // App shortcuts that used to be automatically added to Launcher
+                                    // didn't always have the correct intent flags set, so do that
+                                    // here
+                                    if (intent.getAction() != null &&
+                                        intent.getCategories() != null &&
+                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
+                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                                        intent.addFlags(
+                                            Intent.FLAG_ACTIVITY_NEW_TASK |
+                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                                    }
+                                }
+
+                                if (info != null) {
+                                    info.id = id;
+                                    info.intent = intent;
+                                    container = c.getInt(containerIndex);
+                                    info.container = container;
+                                    info.screenId = c.getInt(screenIndex);
+                                    info.cellX = c.getInt(cellXIndex);
+                                    info.cellY = c.getInt(cellYIndex);
+                                    info.spanX = 1;
+                                    info.spanY = 1;
+                                    // Skip loading items that are out of bounds
+                                    if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                                        if (checkItemDimensions(info)) {
+                                            Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: "
+                                                    + info + ", " + grid.numColumns + "x" + grid.numRows, true);
+                                            continue;
+                                        }
+                                    }
+                                    // check & update map of what's occupied
+                                    deleteOnItemOverlap.set(false);
+                                    if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
+                                        if (deleteOnItemOverlap.get()) {
+                                            itemsToRemove.add(id);
+                                        }
+                                        break;
+                                    }
+
+                                    switch (container) {
+                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+                                        sBgWorkspaceItems.add(info);
+                                        break;
+                                    default:
+                                        // Item is in a user folder
+                                        FolderInfo folderInfo =
+                                                findOrMakeFolder(sBgFolders, container);
+                                        folderInfo.add(info);
+                                        break;
+                                    }
+                                    sBgItemsIdMap.put(info.id, info);
+
+                                    // now that we've loaded everthing re-save it with the
+                                    // icon in case it disappears somehow.
+                                    queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
+                                } else {
+                                    throw new RuntimeException("Unexpected null ShortcutInfo");
+                                }
+                                break;
+
+                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                                id = c.getLong(idIndex);
+                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+
+                                folderInfo.title = c.getString(titleIndex);
+                                folderInfo.id = id;
+                                container = c.getInt(containerIndex);
+                                folderInfo.container = container;
+                                folderInfo.screenId = c.getInt(screenIndex);
+                                folderInfo.cellX = c.getInt(cellXIndex);
+                                folderInfo.cellY = c.getInt(cellYIndex);
+                                folderInfo.spanX = 1;
+                                folderInfo.spanY = 1;
+
+                                // Skip loading items that are out of bounds
+                                if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                                    if (checkItemDimensions(folderInfo)) {
+                                        Log.d(TAG, "Skipped loading out of bounds folder");
+                                        continue;
+                                    }
+                                }
+                                // check & update map of what's occupied
+                                deleteOnItemOverlap.set(false);
+                                if (!checkItemPlacement(occupied, folderInfo,
+                                        deleteOnItemOverlap)) {
+                                    if (deleteOnItemOverlap.get()) {
+                                        itemsToRemove.add(id);
+                                    }
+                                    break;
+                                }
+
+                                switch (container) {
+                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+                                        sBgWorkspaceItems.add(folderInfo);
+                                        break;
+                                }
+
+                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
+                                sBgFolders.put(folderInfo.id, folderInfo);
+                                break;
+
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                                // Read all Launcher-specific widget details
+                                int appWidgetId = c.getInt(appWidgetIdIndex);
+                                String savedProvider = c.getString(appWidgetProviderIndex);
+
+                                id = c.getLong(idIndex);
+
+                                final AppWidgetProviderInfo provider =
+                                        widgets.getAppWidgetInfo(appWidgetId);
+
+                                if (!isSafeMode && (provider == null || provider.provider == null ||
+                                        provider.provider.getPackageName() == null)) {
+                                    String log = "Deleting widget that isn't installed anymore: id="
+                                        + id + " appWidgetId=" + appWidgetId;
+                                    Log.e(TAG, log);
+                                    Launcher.addDumpLog(TAG, log, false);
+                                    itemsToRemove.add(id);
+                                } else {
+                                    appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
+                                            provider.provider);
+                                    appWidgetInfo.id = id;
+                                    appWidgetInfo.screenId = c.getInt(screenIndex);
+                                    appWidgetInfo.cellX = c.getInt(cellXIndex);
+                                    appWidgetInfo.cellY = c.getInt(cellYIndex);
+                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
+                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
+                                    int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
+                                    appWidgetInfo.minSpanX = minSpan[0];
+                                    appWidgetInfo.minSpanY = minSpan[1];
+
+                                    container = c.getInt(containerIndex);
+                                    if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                                        container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                                        Log.e(TAG, "Widget found where container != " +
+                                            "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                        continue;
+                                    }
+
+                                    appWidgetInfo.container = c.getInt(containerIndex);
+                                    // Skip loading items that are out of bounds
+                                    if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                                        if (checkItemDimensions(appWidgetInfo)) {
+                                            Log.d(TAG, "Skipped loading out of bounds app widget");
+                                            continue;
+                                        }
+                                    }
+                                    // check & update map of what's occupied
+                                    deleteOnItemOverlap.set(false);
+                                    if (!checkItemPlacement(occupied, appWidgetInfo,
+                                            deleteOnItemOverlap)) {
+                                        if (deleteOnItemOverlap.get()) {
+                                            itemsToRemove.add(id);
+                                        }
+                                        break;
+                                    }
+                                    String providerName = provider.provider.flattenToString();
+                                    if (!providerName.equals(savedProvider)) {
+                                        ContentValues values = new ContentValues();
+                                        values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
+                                                providerName);
+                                        String where = BaseColumns._ID + "= ?";
+                                        String[] args = {Integer.toString(c.getInt(idIndex))};
+                                        contentResolver.update(contentUri, values, where, args);
+                                    }
+                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
+                                    sBgAppWidgets.add(appWidgetInfo);
+                                }
+                                break;
+                            }
+                        } catch (Exception e) {
+                            Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true);
+                        }
+                    }
+                } finally {
+                    if (c != null) {
+                        c.close();
+                    }
+                }
+
+                // Break early if we've stopped loading
+                if (mStopped) {
+                    clearSBgDataStructures();
+                    return false;
+                }
+
+                if (itemsToRemove.size() > 0) {
+                    ContentProviderClient client = contentResolver.acquireContentProviderClient(
+                            LauncherSettings.Favorites.CONTENT_URI);
+                    // Remove dead items
+                    for (long id : itemsToRemove) {
+                        if (DEBUG_LOADERS) {
+                            Log.d(TAG, "Removed id = " + id);
+                        }
+                        // Don't notify content observers
+                        try {
+                            client.delete(LauncherSettings.Favorites.getContentUri(id, false),
+                                    null, null);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Could not remove id = " + id);
+                        }
+                    }
+                }
+
+                if (loadedOldDb) {
+                    long maxScreenId = 0;
+                    // If we're importing we use the old screen order.
+                    for (ItemInfo item: sBgItemsIdMap.values()) {
+                        long screenId = item.screenId;
+                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                                !sBgWorkspaceScreens.contains(screenId)) {
+                            sBgWorkspaceScreens.add(screenId);
+                            if (screenId > maxScreenId) {
+                                maxScreenId = screenId;
+                            }
+                        }
+                    }
+                    Collections.sort(sBgWorkspaceScreens);
+
+                    LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
+                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+
+                    // Update the max item id after we load an old db
+                    long maxItemId = 0;
+                    // If we're importing we use the old screen order.
+                    for (ItemInfo item: sBgItemsIdMap.values()) {
+                        maxItemId = Math.max(maxItemId, item.id);
+                    }
+                    LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
+                } else {
+                    TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
+                    for (Integer i : orderedScreens.keySet()) {
+                        sBgWorkspaceScreens.add(orderedScreens.get(i));
+                    }
+
+                    // Remove any empty screens
+                    ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
+                    for (ItemInfo item: sBgItemsIdMap.values()) {
+                        long screenId = item.screenId;
+                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                                unusedScreens.contains(screenId)) {
+                            unusedScreens.remove(screenId);
+                        }
+                    }
+
+                    // If there are any empty screens remove them, and update.
+                    if (unusedScreens.size() != 0) {
+                        sBgWorkspaceScreens.removeAll(unusedScreens);
+                        updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+                    }
+                }
+
+                if (DEBUG_LOADERS) {
+                    Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
+                    Log.d(TAG, "workspace layout: ");
+                    int nScreens = occupied.size();
+                    for (int y = 0; y < countY; y++) {
+                        String line = "";
+
+                        Iterator<Long> iter = occupied.keySet().iterator();
+                        while (iter.hasNext()) {
+                            long screenId = iter.next();
+                            if (screenId > 0) {
+                                line += " | ";
+                            }
+                            for (int x = 0; x < countX; x++) {
+                                line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
+                            }
+                        }
+                        Log.d(TAG, "[ " + line + " ]");
+                    }
+                }
+            }
+            return loadedOldDb;
+        }
+
+        /** Filters the set of items who are directly or indirectly (via another container) on the
+         * specified screen. */
+        private void filterCurrentWorkspaceItems(int currentScreen,
+                ArrayList<ItemInfo> allWorkspaceItems,
+                ArrayList<ItemInfo> currentScreenItems,
+                ArrayList<ItemInfo> otherScreenItems) {
+            // Purge any null ItemInfos
+            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
+            while (iter.hasNext()) {
+                ItemInfo i = iter.next();
+                if (i == null) {
+                    iter.remove();
+                }
+            }
+
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // items given.
+            if (currentScreen < 0) {
+                currentScreenItems.addAll(allWorkspaceItems);
+            }
+
+            // Order the set of items by their containers first, this allows use to walk through the
+            // list sequentially, build up a list of containers that are in the specified screen,
+            // as well as all items in those containers.
+            Set<Long> itemsOnScreen = new HashSet<Long>();
+            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
+                @Override
+                public int compare(ItemInfo lhs, ItemInfo rhs) {
+                    return (int) (lhs.container - rhs.container);
+                }
+            });
+            for (ItemInfo info : allWorkspaceItems) {
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    if (info.screenId == currentScreen) {
+                        currentScreenItems.add(info);
+                        itemsOnScreen.add(info.id);
+                    } else {
+                        otherScreenItems.add(info);
+                    }
+                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    if (itemsOnScreen.contains(info.container)) {
+                        currentScreenItems.add(info);
+                        itemsOnScreen.add(info.id);
+                    } else {
+                        otherScreenItems.add(info);
+                    }
+                }
+            }
+        }
+
+        /** Filters the set of widgets which are on the specified screen. */
+        private void filterCurrentAppWidgets(int currentScreen,
+                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
+                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // widgets given.
+            if (currentScreen < 0) {
+                currentScreenWidgets.addAll(appWidgets);
+            }
+
+            for (LauncherAppWidgetInfo widget : appWidgets) {
+                if (widget == null) continue;
+                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                        widget.screenId == currentScreen) {
+                    currentScreenWidgets.add(widget);
+                } else {
+                    otherScreenWidgets.add(widget);
+                }
+            }
+        }
+
+        /** Filters the set of folders which are on the specified screen. */
+        private void filterCurrentFolders(int currentScreen,
+                HashMap<Long, ItemInfo> itemsIdMap,
+                HashMap<Long, FolderInfo> folders,
+                HashMap<Long, FolderInfo> currentScreenFolders,
+                HashMap<Long, FolderInfo> otherScreenFolders) {
+            // If we aren't filtering on a screen, then the set of items to load is the full set of
+            // widgets given.
+            if (currentScreen < 0) {
+                currentScreenFolders.putAll(folders);
+            }
+
+            for (long id : folders.keySet()) {
+                ItemInfo info = itemsIdMap.get(id);
+                FolderInfo folder = folders.get(id);
+                if (info == null || folder == null) continue;
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                        info.screenId == currentScreen) {
+                    currentScreenFolders.put(id, folder);
+                } else {
+                    otherScreenFolders.put(id, folder);
+                }
+            }
+        }
+
+        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
+         * right) */
+        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
+            final LauncherAppState app = LauncherAppState.getInstance();
+            final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            // XXX: review this
+            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
+                @Override
+                public int compare(ItemInfo lhs, ItemInfo rhs) {
+                    int cellCountX = (int) grid.numColumns;
+                    int cellCountY = (int) grid.numRows;
+                    int screenOffset = cellCountX * cellCountY;
+                    int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
+                    long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
+                            lhs.cellY * cellCountX + lhs.cellX);
+                    long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
+                            rhs.cellY * cellCountX + rhs.cellX);
+                    return (int) (lr - rr);
+                }
+            });
+        }
+
+        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
+                final ArrayList<Long> orderedScreens) {
+            final Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.bindScreens(orderedScreens);
+                    }
+                }
+            };
+            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+        }
+
+        private void bindWorkspaceItems(final Callbacks oldCallbacks,
+                final ArrayList<ItemInfo> workspaceItems,
+                final ArrayList<LauncherAppWidgetInfo> appWidgets,
+                final HashMap<Long, FolderInfo> folders,
+                ArrayList<Runnable> deferredBindRunnables) {
+
+            final boolean postOnMainThread = (deferredBindRunnables != null);
+
+            // Bind the workspace items
+            int N = workspaceItems.size();
+            for (int i = 0; i < N; i += ITEMS_CHUNK) {
+                final int start = i;
+                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
+                final Runnable r = new Runnable() {
+                    @Override
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
+                                    false);
+                        }
+                    }
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                }
+            }
+
+            // Bind the folders
+            if (!folders.isEmpty()) {
+                final Runnable r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.bindFolders(folders);
+                        }
+                    }
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                }
+            }
+
+            // Bind the widgets, one at a time
+            N = appWidgets.size();
+            for (int i = 0; i < N; i++) {
+                final LauncherAppWidgetInfo widget = appWidgets.get(i);
+                final Runnable r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.bindAppWidget(widget);
+                        }
+                    }
+                };
+                if (postOnMainThread) {
+                    deferredBindRunnables.add(r);
+                } else {
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                }
+            }
+        }
+
+        /**
+         * Binds all loaded data to actual views on the main thread.
+         */
+        private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
+            final long t = SystemClock.uptimeMillis();
+            Runnable r;
+
+            // Don't use these two variables in any of the callback runnables.
+            // Otherwise we hold a reference to them.
+            final Callbacks oldCallbacks = mCallbacks.get();
+            if (oldCallbacks == null) {
+                // This launcher has exited and nobody bothered to tell us.  Just bail.
+                Log.w(TAG, "LoaderTask running with no launcher");
+                return;
+            }
+
+            final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
+            final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
+                oldCallbacks.getCurrentWorkspaceScreen();
+
+            // Load all the items that are on the current page first (and in the process, unbind
+            // all the existing workspace items before we call startBinding() below.
+            unbindWorkspaceItemsOnMainThread();
+            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<LauncherAppWidgetInfo> appWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
+            HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
+            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
+            synchronized (sBgLock) {
+                workspaceItems.addAll(sBgWorkspaceItems);
+                appWidgets.addAll(sBgAppWidgets);
+                folders.putAll(sBgFolders);
+                itemsIdMap.putAll(sBgItemsIdMap);
+                orderedScreenIds.addAll(sBgWorkspaceScreens);
+            }
+
+            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
+            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
+                    new ArrayList<LauncherAppWidgetInfo>();
+            HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
+            HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
+
+            // Separate the items that are on the current screen, and all the other remaining items
+            filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
+                    otherWorkspaceItems);
+            filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
+                    otherAppWidgets);
+            filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
+                    otherFolders);
+            sortWorkspaceItemsSpatially(currentWorkspaceItems);
+            sortWorkspaceItemsSpatially(otherWorkspaceItems);
+
+            // Tell the workspace that we're about to start binding items
+            r = new Runnable() {
+                public void run() {
+                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.startBinding();
+                    }
+                }
+            };
+            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+
+            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
+
+            // Load items on the current page
+            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
+                    currentFolders, null);
+            if (isLoadingSynchronously) {
+                r = new Runnable() {
+                    public void run() {
+                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                        if (callbacks != null) {
+                            callbacks.onPageBoundSynchronously(currentScreen);
+                        }
+                    }
+                };
+                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+            }
+
+            // Load all the remaining pages (if we are loading synchronously, we want to defer this
+            // work until after the first render)
+            mDeferredBindRunnables.clear();
+            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
+                    (isLoadingSynchronously ? mDeferredBindRunnables : null));
+
+            // Tell the workspace that we're done binding items
+            r = new Runnable() {
+                public void run() {
+                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.finishBindingItems(isUpgradePath);
+                    }
+
+                    // If we're profiling, ensure this is the last thing in the queue.
+                    if (DEBUG_LOADERS) {
+                        Log.d(TAG, "bound workspace in "
+                            + (SystemClock.uptimeMillis()-t) + "ms");
+                    }
+
+                    mIsLoadingAndBindingWorkspace = false;
+                }
+            };
+            if (isLoadingSynchronously) {
+                mDeferredBindRunnables.add(r);
+            } else {
+                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+            }
+        }
+
+        private void loadAndBindAllApps() {
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
+            }
+            if (!mAllAppsLoaded) {
+                loadAllApps();
+                synchronized (LoaderTask.this) {
+                    if (mStopped) {
+                        return;
+                    }
+                    mAllAppsLoaded = true;
+                }
+            } else {
+                onlyBindAllApps();
+            }
+        }
+
+        private void onlyBindAllApps() {
+            final Callbacks oldCallbacks = mCallbacks.get();
+            if (oldCallbacks == null) {
+                // This launcher has exited and nobody bothered to tell us.  Just bail.
+                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
+                return;
+            }
+
+            // shallow copy
+            @SuppressWarnings("unchecked")
+            final ArrayList<AppInfo> list
+                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
+            Runnable r = new Runnable() {
+                public void run() {
+                    final long t = SystemClock.uptimeMillis();
+                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.bindAllApplications(list);
+                    }
+                    if (DEBUG_LOADERS) {
+                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
+                                + (SystemClock.uptimeMillis()-t) + "ms");
+                    }
+                }
+            };
+            boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
+            if (isRunningOnMainThread) {
+                r.run();
+            } else {
+                mHandler.post(r);
+            }
+        }
+
+        private void loadAllApps() {
+            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+            final Callbacks oldCallbacks = mCallbacks.get();
+            if (oldCallbacks == null) {
+                // This launcher has exited and nobody bothered to tell us.  Just bail.
+                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
+                return;
+            }
+
+            final PackageManager packageManager = mContext.getPackageManager();
+            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+            // Clear the list of apps
+            mBgAllAppsList.clear();
+
+            // Query for the set of apps
+            final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+            List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "queryIntentActivities took "
+                        + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+                Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
+            }
+            // Fail if we don't have any apps
+            if (apps == null || apps.isEmpty()) {
+                return;
+            }
+            // Sort the applications by name
+            final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+            Collections.sort(apps,
+                    new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "sort took "
+                        + (SystemClock.uptimeMillis()-sortTime) + "ms");
+            }
+
+            // Create the ApplicationInfos
+            for (int i = 0; i < apps.size(); i++) {
+                ResolveInfo app = apps.get(i);
+                // This builds the icon bitmaps.
+                mBgAllAppsList.add(new AppInfo(packageManager, app,
+                        mIconCache, mLabelCache));
+            }
+
+            // Huh? Shouldn't this be inside the Runnable below?
+            final ArrayList<AppInfo> added = mBgAllAppsList.added;
+            mBgAllAppsList.added = new ArrayList<AppInfo>();
+
+            // Post callback on main thread
+            mHandler.post(new Runnable() {
+                public void run() {
+                    final long bindTime = SystemClock.uptimeMillis();
+                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.bindAllApplications(added);
+                        if (DEBUG_LOADERS) {
+                            Log.d(TAG, "bound " + added.size() + " apps in "
+                                + (SystemClock.uptimeMillis() - bindTime) + "ms");
+                        }
+                    } else {
+                        Log.i(TAG, "not binding apps: no Launcher activity");
+                    }
+                }
+            });
+
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "Icons processed in "
+                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
+            }
+        }
+
+        public void dumpState() {
+            synchronized (sBgLock) {
+                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
+                Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
+                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
+                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
+                Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
+            }
+        }
+    }
+
+    void enqueuePackageUpdated(PackageUpdatedTask task) {
+        sWorker.post(task);
+    }
+
+    private class PackageUpdatedTask implements Runnable {
+        int mOp;
+        String[] mPackages;
+
+        public static final int OP_NONE = 0;
+        public static final int OP_ADD = 1;
+        public static final int OP_UPDATE = 2;
+        public static final int OP_REMOVE = 3; // uninstlled
+        public static final int OP_UNAVAILABLE = 4; // external media unmounted
+
+
+        public PackageUpdatedTask(int op, String[] packages) {
+            mOp = op;
+            mPackages = packages;
+        }
+
+        public void run() {
+            final Context context = mApp.getContext();
+
+            final String[] packages = mPackages;
+            final int N = packages.length;
+            switch (mOp) {
+                case OP_ADD:
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+                        mBgAllAppsList.addPackage(context, packages[i]);
+                    }
+                    break;
+                case OP_UPDATE:
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                        mBgAllAppsList.updatePackage(context, packages[i]);
+                        WidgetPreviewLoader.removePackageFromDb(
+                                mApp.getWidgetPreviewCacheDb(), packages[i]);
+                    }
+                    break;
+                case OP_REMOVE:
+                case OP_UNAVAILABLE:
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+                        mBgAllAppsList.removePackage(packages[i]);
+                        WidgetPreviewLoader.removePackageFromDb(
+                                mApp.getWidgetPreviewCacheDb(), packages[i]);
+                    }
+                    break;
+            }
+
+            ArrayList<AppInfo> added = null;
+            ArrayList<AppInfo> modified = null;
+            final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+            if (mBgAllAppsList.added.size() > 0) {
+                added = new ArrayList<AppInfo>(mBgAllAppsList.added);
+                mBgAllAppsList.added.clear();
+            }
+            if (mBgAllAppsList.modified.size() > 0) {
+                modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
+                mBgAllAppsList.modified.clear();
+            }
+            if (mBgAllAppsList.removed.size() > 0) {
+                removedApps.addAll(mBgAllAppsList.removed);
+                mBgAllAppsList.removed.clear();
+            }
+
+            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+            if (callbacks == null) {
+                Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
+                return;
+            }
+
+            if (added != null) {
+                // Ensure that we add all the workspace applications to the db
+                Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+                    addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
+                } else {
+                    final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
+                    addAndBindAddedApps(context, addedInfos, cb, added);
+                }
+            }
+            if (modified != null) {
+                final ArrayList<AppInfo> modifiedFinal = modified;
+
+                // Update the launcher db to reflect the changes
+                for (AppInfo a : modifiedFinal) {
+                    ArrayList<ItemInfo> infos =
+                            getItemInfoForComponentName(a.componentName);
+                    for (ItemInfo i : infos) {
+                        if (isShortcutInfoUpdateable(i)) {
+                            ShortcutInfo info = (ShortcutInfo) i;
+                            info.title = a.title.toString();
+                            updateItemInDatabase(context, info);
+                        }
+                    }
+                }
+
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        if (callbacks == cb && cb != null) {
+                            callbacks.bindAppsUpdated(modifiedFinal);
+                        }
+                    }
+                });
+            }
+            // If a package has been removed, or an app has been removed as a result of
+            // an update (for example), make the removed callback.
+            if (mOp == OP_REMOVE || !removedApps.isEmpty()) {
+                final boolean packageRemoved = (mOp == OP_REMOVE);
+                final ArrayList<String> removedPackageNames =
+                        new ArrayList<String>(Arrays.asList(packages));
+
+                // Update the launcher db to reflect the removal of apps
+                if (packageRemoved) {
+                    for (String pn : removedPackageNames) {
+                        ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+                        for (ItemInfo i : infos) {
+                            deleteItemFromDatabase(context, i);
+                        }
+                    }
+
+                    // Remove any queued items from the install queue
+                    String spKey = LauncherAppState.getSharedPreferencesKey();
+                    SharedPreferences sp =
+                            context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+                    InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
+                } else {
+                    for (AppInfo a : removedApps) {
+                        ArrayList<ItemInfo> infos =
+                                getItemInfoForComponentName(a.componentName);
+                        for (ItemInfo i : infos) {
+                            deleteItemFromDatabase(context, i);
+                        }
+                    }
+                }
+
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        if (callbacks == cb && cb != null) {
+                            callbacks.bindComponentsRemoved(removedPackageNames,
+                                    removedApps, packageRemoved);
+                        }
+                    }
+                });
+            }
+
+            final ArrayList<Object> widgetsAndShortcuts =
+                getSortedWidgetsAndShortcuts(context);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                    if (callbacks == cb && cb != null) {
+                        callbacks.bindPackagesUpdated(widgetsAndShortcuts);
+                    }
+                }
+            });
+
+            // Write all the logs to disk
+            mHandler.post(new Runnable() {
+                public void run() {
+                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                    if (callbacks == cb && cb != null) {
+                        callbacks.dumpLogsToLocalData();
+                    }
+                }
+            });
+        }
+    }
+
+    // Returns a list of ResolveInfos/AppWindowInfos in sorted order
+    public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
+        widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
+        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+        Collections.sort(widgetsAndShortcuts,
+            new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
+        return widgetsAndShortcuts;
+    }
+
+    private boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
+        if (cn == null) {
+            return false;
+        }
+
+        try {
+            // Skip if the application is disabled
+            PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
+            if (!pi.applicationInfo.enabled) {
+                return false;
+            }
+
+            // Check the activity
+            return (pm.getActivityInfo(cn, 0) != null);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * This is called from the code that adds shortcuts from the intent receiver.  This
+     * doesn't have a Cursor, but
+     */
+    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
+        return getShortcutInfo(manager, intent, context, null, -1, -1, null);
+    }
+
+    /**
+     * Make an ShortcutInfo object for a shortcut that is an application.
+     *
+     * If c is not null, then it will be used to fill in missing data like the title and icon.
+     */
+    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
+            Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
+        ComponentName componentName = intent.getComponent();
+        final ShortcutInfo info = new ShortcutInfo();
+        if (componentName != null && !isValidPackageComponent(manager, componentName)) {
+            Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
+            return null;
+        } else {
+            try {
+                PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
+                info.initFlagsAndFirstInstallTime(pi);
+            } catch (NameNotFoundException e) {
+                Log.d(TAG, "getPackInfo failed for package " +
+                        componentName.getPackageName());
+            }
+        }
+
+        // TODO: See if the PackageManager knows about this case.  If it doesn't
+        // then return null & delete this.
+
+        // the resource -- This may implicitly give us back the fallback icon,
+        // but don't worry about that.  All we're doing with usingFallbackIcon is
+        // to avoid saving lots of copies of that in the database, and most apps
+        // have icons anyway.
+
+        // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
+        // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
+        // via resolveActivity().
+        Bitmap icon = null;
+        ResolveInfo resolveInfo = null;
+        ComponentName oldComponent = intent.getComponent();
+        Intent newIntent = new Intent(intent.getAction(), null);
+        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        newIntent.setPackage(oldComponent.getPackageName());
+        List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
+        for (ResolveInfo i : infos) {
+            ComponentName cn = new ComponentName(i.activityInfo.packageName,
+                    i.activityInfo.name);
+            if (cn.equals(oldComponent)) {
+                resolveInfo = i;
+            }
+        }
+        if (resolveInfo == null) {
+            resolveInfo = manager.resolveActivity(intent, 0);
+        }
+        if (resolveInfo != null) {
+            icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
+        }
+        // the db
+        if (icon == null) {
+            if (c != null) {
+                icon = getIconFromCursor(c, iconIndex, context);
+            }
+        }
+        // the fallback icon
+        if (icon == null) {
+            icon = getFallbackIcon();
+            info.usingFallbackIcon = true;
+        }
+        info.setIcon(icon);
+
+        // from the resource
+        if (resolveInfo != null) {
+            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
+            if (labelCache != null && labelCache.containsKey(key)) {
+                info.title = labelCache.get(key);
+            } else {
+                info.title = resolveInfo.activityInfo.loadLabel(manager);
+                if (labelCache != null) {
+                    labelCache.put(key, info.title);
+                }
+            }
+        }
+        // from the db
+        if (info.title == null) {
+            if (c != null) {
+                info.title =  c.getString(titleIndex);
+            }
+        }
+        // fall back to the class name of the activity
+        if (info.title == null) {
+            info.title = componentName.getClassName();
+        }
+        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        return info;
+    }
+
+    static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
+            ItemInfoFilter f) {
+        HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
+        for (ItemInfo i : infos) {
+            if (i instanceof ShortcutInfo) {
+                ShortcutInfo info = (ShortcutInfo) i;
+                ComponentName cn = info.intent.getComponent();
+                if (cn != null && f.filterItem(null, info, cn)) {
+                    filtered.add(info);
+                }
+            } else if (i instanceof FolderInfo) {
+                FolderInfo info = (FolderInfo) i;
+                for (ShortcutInfo s : info.contents) {
+                    ComponentName cn = s.intent.getComponent();
+                    if (cn != null && f.filterItem(info, s, cn)) {
+                        filtered.add(s);
+                    }
+                }
+            } else if (i instanceof LauncherAppWidgetInfo) {
+                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+                ComponentName cn = info.providerName;
+                if (cn != null && f.filterItem(null, info, cn)) {
+                    filtered.add(info);
+                }
+            }
+        }
+        return new ArrayList<ItemInfo>(filtered);
+    }
+
+    private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
+        ItemInfoFilter filter  = new ItemInfoFilter() {
+            @Override
+            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
+                return cn.getPackageName().equals(pn);
+            }
+        };
+        return filterItemInfos(sBgItemsIdMap.values(), filter);
+    }
+
+    private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
+        ItemInfoFilter filter  = new ItemInfoFilter() {
+            @Override
+            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
+                return cn.equals(cname);
+            }
+        };
+        return filterItemInfos(sBgItemsIdMap.values(), filter);
+    }
+
+    public static boolean isShortcutInfoUpdateable(ItemInfo i) {
+        if (i instanceof ShortcutInfo) {
+            ShortcutInfo info = (ShortcutInfo) i;
+            // We need to check for ACTION_MAIN otherwise getComponent() might
+            // return null for some shortcuts (for instance, for shortcuts to
+            // web pages.)
+            Intent intent = info.intent;
+            ComponentName name = intent.getComponent();
+            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+                    Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Make an ShortcutInfo object for a shortcut that isn't an application.
+     */
+    private ShortcutInfo getShortcutInfo(Cursor c, Context context,
+            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
+            int titleIndex) {
+
+        Bitmap icon = null;
+        final ShortcutInfo info = new ShortcutInfo();
+        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+
+        // TODO: If there's an explicit component and we can't install that, delete it.
+
+        info.title = c.getString(titleIndex);
+
+        int iconType = c.getInt(iconTypeIndex);
+        switch (iconType) {
+        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
+            String packageName = c.getString(iconPackageIndex);
+            String resourceName = c.getString(iconResourceIndex);
+            PackageManager packageManager = context.getPackageManager();
+            info.customIcon = false;
+            // the resource
+            try {
+                Resources resources = packageManager.getResourcesForApplication(packageName);
+                if (resources != null) {
+                    final int id = resources.getIdentifier(resourceName, null, null);
+                    icon = Utilities.createIconBitmap(
+                            mIconCache.getFullResIcon(resources, id), context);
+                }
+            } catch (Exception e) {
+                // drop this.  we have other places to look for icons
+            }
+            // the db
+            if (icon == null) {
+                icon = getIconFromCursor(c, iconIndex, context);
+            }
+            // the fallback icon
+            if (icon == null) {
+                icon = getFallbackIcon();
+                info.usingFallbackIcon = true;
+            }
+            break;
+        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
+            icon = getIconFromCursor(c, iconIndex, context);
+            if (icon == null) {
+                icon = getFallbackIcon();
+                info.customIcon = false;
+                info.usingFallbackIcon = true;
+            } else {
+                info.customIcon = true;
+            }
+            break;
+        default:
+            icon = getFallbackIcon();
+            info.usingFallbackIcon = true;
+            info.customIcon = false;
+            break;
+        }
+        info.setIcon(icon);
+        return info;
+    }
+
+    Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
+        @SuppressWarnings("all") // suppress dead code warning
+        final boolean debug = false;
+        if (debug) {
+            Log.d(TAG, "getIconFromCursor app="
+                    + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
+        }
+        byte[] data = c.getBlob(iconIndex);
+        try {
+            return Utilities.createIconBitmap(
+                    BitmapFactory.decodeByteArray(data, 0, data.length), context);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
+            int cellX, int cellY, boolean notify) {
+        final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
+        if (info == null) {
+            return null;
+        }
+        addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
+
+        return info;
+    }
+
+    /**
+     * Attempts to find an AppWidgetProviderInfo that matches the given component.
+     */
+    AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
+            ComponentName component) {
+        List<AppWidgetProviderInfo> widgets =
+            AppWidgetManager.getInstance(context).getInstalledProviders();
+        for (AppWidgetProviderInfo info : widgets) {
+            if (info.provider.equals(component)) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a list of all the widgets that can handle configuration with a particular mimeType.
+     */
+    List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
+        final PackageManager packageManager = context.getPackageManager();
+        final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
+            new ArrayList<WidgetMimeTypeHandlerData>();
+
+        final Intent supportsIntent =
+            new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
+        supportsIntent.setType(mimeType);
+
+        // Create a set of widget configuration components that we can test against
+        final List<AppWidgetProviderInfo> widgets =
+            AppWidgetManager.getInstance(context).getInstalledProviders();
+        final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
+            new HashMap<ComponentName, AppWidgetProviderInfo>();
+        for (AppWidgetProviderInfo info : widgets) {
+            configurationComponentToWidget.put(info.configure, info);
+        }
+
+        // Run through each of the intents that can handle this type of clip data, and cross
+        // reference them with the components that are actual configuration components
+        final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        for (ResolveInfo info : activities) {
+            final ActivityInfo activityInfo = info.activityInfo;
+            final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
+                    activityInfo.name);
+            if (configurationComponentToWidget.containsKey(infoComponent)) {
+                supportedConfigurationActivities.add(
+                        new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
+                                configurationComponentToWidget.get(infoComponent)));
+            }
+        }
+        return supportedConfigurationActivities;
+    }
+
+    ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
+        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+
+        if (intent == null) {
+            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
+            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
+            return null;
+        }
+
+        Bitmap icon = null;
+        boolean customIcon = false;
+        ShortcutIconResource iconResource = null;
+
+        if (bitmap != null && bitmap instanceof Bitmap) {
+            icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
+            customIcon = true;
+        } else {
+            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+            if (extra != null && extra instanceof ShortcutIconResource) {
+                try {
+                    iconResource = (ShortcutIconResource) extra;
+                    final PackageManager packageManager = context.getPackageManager();
+                    Resources resources = packageManager.getResourcesForApplication(
+                            iconResource.packageName);
+                    final int id = resources.getIdentifier(iconResource.resourceName, null, null);
+                    icon = Utilities.createIconBitmap(
+                            mIconCache.getFullResIcon(resources, id), context);
+                } catch (Exception e) {
+                    Log.w(TAG, "Could not load shortcut icon: " + extra);
+                }
+            }
+        }
+
+        final ShortcutInfo info = new ShortcutInfo();
+
+        if (icon == null) {
+            if (fallbackIcon != null) {
+                icon = fallbackIcon;
+            } else {
+                icon = getFallbackIcon();
+                info.usingFallbackIcon = true;
+            }
+        }
+        info.setIcon(icon);
+
+        info.title = name;
+        info.intent = intent;
+        info.customIcon = customIcon;
+        info.iconResource = iconResource;
+
+        return info;
+    }
+
+    boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
+            int iconIndex) {
+        // If apps can't be on SD, don't even bother.
+        if (!mAppsCanBeOnRemoveableStorage) {
+            return false;
+        }
+        // If this icon doesn't have a custom icon, check to see
+        // what's stored in the DB, and if it doesn't match what
+        // we're going to show, store what we are going to show back
+        // into the DB.  We do this so when we're loading, if the
+        // package manager can't find an icon (for example because
+        // the app is on SD) then we can use that instead.
+        if (!info.customIcon && !info.usingFallbackIcon) {
+            cache.put(info, c.getBlob(iconIndex));
+            return true;
+        }
+        return false;
+    }
+    void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
+        boolean needSave = false;
+        try {
+            if (data != null) {
+                Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
+                Bitmap loaded = info.getIcon(mIconCache);
+                needSave = !saved.sameAs(loaded);
+            } else {
+                needSave = true;
+            }
+        } catch (Exception e) {
+            needSave = true;
+        }
+        if (needSave) {
+            Log.d(TAG, "going to save icon bitmap for info=" + info);
+            // This is slower than is ideal, but this only happens once
+            // or when the app is updated with a new icon.
+            updateItemInDatabase(context, info);
+        }
+    }
+
+    /**
+     * Return an existing FolderInfo object if we have encountered this ID previously,
+     * or make a new one.
+     */
+    private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
+        // See if a placeholder was created for us already
+        FolderInfo folderInfo = folders.get(id);
+        if (folderInfo == null) {
+            // No placeholder -- create a new instance
+            folderInfo = new FolderInfo();
+            folders.put(id, folderInfo);
+        }
+        return folderInfo;
+    }
+
+    public static final Comparator<AppInfo> getAppNameComparator() {
+        final Collator collator = Collator.getInstance();
+        return new Comparator<AppInfo>() {
+            public final int compare(AppInfo a, AppInfo b) {
+                int result = collator.compare(a.title.toString().trim(),
+                        b.title.toString().trim());
+                if (result == 0) {
+                    result = a.componentName.compareTo(b.componentName);
+                }
+                return result;
+            }
+        };
+    }
+    public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
+            = new Comparator<AppInfo>() {
+        public final int compare(AppInfo a, AppInfo b) {
+            if (a.firstInstallTime < b.firstInstallTime) return 1;
+            if (a.firstInstallTime > b.firstInstallTime) return -1;
+            return 0;
+        }
+    };
+    public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
+        final Collator collator = Collator.getInstance();
+        return new Comparator<AppWidgetProviderInfo>() {
+            public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
+                return collator.compare(a.label.toString().trim(), b.label.toString().trim());
+            }
+        };
+    }
+    static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
+        if (info.activityInfo != null) {
+            return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+        } else {
+            return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
+        }
+    }
+    public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
+        private Collator mCollator;
+        private PackageManager mPackageManager;
+        private HashMap<Object, CharSequence> mLabelCache;
+        ShortcutNameComparator(PackageManager pm) {
+            mPackageManager = pm;
+            mLabelCache = new HashMap<Object, CharSequence>();
+            mCollator = Collator.getInstance();
+        }
+        ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
+            mPackageManager = pm;
+            mLabelCache = labelCache;
+            mCollator = Collator.getInstance();
+        }
+        public final int compare(ResolveInfo a, ResolveInfo b) {
+            CharSequence labelA, labelB;
+            ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
+            ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
+            if (mLabelCache.containsKey(keyA)) {
+                labelA = mLabelCache.get(keyA);
+            } else {
+                labelA = a.loadLabel(mPackageManager).toString().trim();
+
+                mLabelCache.put(keyA, labelA);
+            }
+            if (mLabelCache.containsKey(keyB)) {
+                labelB = mLabelCache.get(keyB);
+            } else {
+                labelB = b.loadLabel(mPackageManager).toString().trim();
+
+                mLabelCache.put(keyB, labelB);
+            }
+            return mCollator.compare(labelA, labelB);
+        }
+    };
+    public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
+        private Collator mCollator;
+        private PackageManager mPackageManager;
+        private HashMap<Object, String> mLabelCache;
+        WidgetAndShortcutNameComparator(PackageManager pm) {
+            mPackageManager = pm;
+            mLabelCache = new HashMap<Object, String>();
+            mCollator = Collator.getInstance();
+        }
+        public final int compare(Object a, Object b) {
+            String labelA, labelB;
+            if (mLabelCache.containsKey(a)) {
+                labelA = mLabelCache.get(a);
+            } else {
+                labelA = (a instanceof AppWidgetProviderInfo) ?
+                    ((AppWidgetProviderInfo) a).label :
+                    ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
+                mLabelCache.put(a, labelA);
+            }
+            if (mLabelCache.containsKey(b)) {
+                labelB = mLabelCache.get(b);
+            } else {
+                labelB = (b instanceof AppWidgetProviderInfo) ?
+                    ((AppWidgetProviderInfo) b).label :
+                    ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
+                mLabelCache.put(b, labelB);
+            }
+            return mCollator.compare(labelA, labelB);
+        }
+    };
+
+    public void dumpState() {
+        Log.d(TAG, "mCallbacks=" + mCallbacks);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
+        if (mLoaderTask != null) {
+            mLoaderTask.dumpState();
+        } else {
+            Log.d(TAG, "mLoaderTask=null");
+        }
+    }
+}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
new file mode 100644
index 0000000..d0f6770
--- /dev/null
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -0,0 +1,1452 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.config.ProviderConfig;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LauncherProvider extends ContentProvider {
+    private static final String TAG = "Launcher.LauncherProvider";
+    private static final boolean LOGD = false;
+
+    private static final String DATABASE_NAME = "launcher.db";
+
+    private static final int DATABASE_VERSION = 15;
+
+    static final String OLD_AUTHORITY = "com.android.launcher2.settings";
+    static final String AUTHORITY = ProviderConfig.AUTHORITY;
+
+    static final String TABLE_FAVORITES = "favorites";
+    static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
+    static final String PARAMETER_NOTIFY = "notify";
+    static final String UPGRADED_FROM_OLD_DATABASE =
+            "UPGRADED_FROM_OLD_DATABASE";
+    static final String EMPTY_DATABASE_CREATED =
+            "EMPTY_DATABASE_CREATED";
+    static final String DEFAULT_WORKSPACE_RESOURCE_ID =
+            "DEFAULT_WORKSPACE_RESOURCE_ID";
+
+    private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
+            "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
+
+    /**
+     * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
+     * {@link AppWidgetHost#deleteHost()} is called during database creation.
+     * Use this to recall {@link AppWidgetHost#startListening()} if needed.
+     */
+    static final Uri CONTENT_APPWIDGET_RESET_URI =
+            Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
+
+    private DatabaseHelper mOpenHelper;
+    private static boolean sJustLoadedFromOldDb;
+
+    @Override
+    public boolean onCreate() {
+        final Context context = getContext();
+        mOpenHelper = new DatabaseHelper(context);
+        LauncherAppState.setLauncherProvider(this);
+        return true;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        SqlArguments args = new SqlArguments(uri, null, null);
+        if (TextUtils.isEmpty(args.where)) {
+            return "vnd.android.cursor.dir/" + args.table;
+        } else {
+            return "vnd.android.cursor.item/" + args.table;
+        }
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+
+        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(args.table);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
+        result.setNotificationUri(getContext().getContentResolver(), uri);
+
+        return result;
+    }
+
+    private static long dbInsertAndCheck(DatabaseHelper helper,
+            SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
+        if (!values.containsKey(LauncherSettings.Favorites._ID)) {
+            throw new RuntimeException("Error: attempting to add item without specifying an id");
+        }
+        return db.insert(table, nullColumnHack, values);
+    }
+
+    private static void deleteId(SQLiteDatabase db, long id) {
+        Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
+        SqlArguments args = new SqlArguments(uri, null, null);
+        db.delete(args.table, args.where, args.args);
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues initialValues) {
+        SqlArguments args = new SqlArguments(uri);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        addModifiedTime(initialValues);
+        final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
+        if (rowId <= 0) return null;
+
+        uri = ContentUris.withAppendedId(uri, rowId);
+        sendNotify(uri);
+
+        return uri;
+    }
+
+    @Override
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        SqlArguments args = new SqlArguments(uri);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            int numValues = values.length;
+            for (int i = 0; i < numValues; i++) {
+                addModifiedTime(values[i]);
+                if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
+                    return 0;
+                }
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+
+        sendNotify(uri);
+        return values.length;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int count = db.delete(args.table, args.where, args.args);
+        if (count > 0) sendNotify(uri);
+
+        return count;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+
+        addModifiedTime(values);
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int count = db.update(args.table, values, args.where, args.args);
+        if (count > 0) sendNotify(uri);
+
+        return count;
+    }
+
+    private void sendNotify(Uri uri) {
+        String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
+        if (notify == null || "true".equals(notify)) {
+            getContext().getContentResolver().notifyChange(uri, null);
+        }
+
+        // always notify the backup agent
+        LauncherBackupAgentHelper.dataChanged(getContext());
+    }
+
+    private void addModifiedTime(ContentValues values) {
+        values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
+    }
+
+    public long generateNewItemId() {
+        return mOpenHelper.generateNewItemId();
+    }
+
+    public void updateMaxItemId(long id) {
+        mOpenHelper.updateMaxItemId(id);
+    }
+
+    public long generateNewScreenId() {
+        return mOpenHelper.generateNewScreenId();
+    }
+
+    // This is only required one time while loading the workspace during the
+    // upgrade path, and should never be called from anywhere else.
+    public void updateMaxScreenId(long maxScreenId) {
+        mOpenHelper.updateMaxScreenId(maxScreenId);
+    }
+
+    /**
+     * @param Should we load the old db for upgrade? first run only.
+     */
+    synchronized public boolean justLoadedOldDb() {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
+
+        boolean loadedOldDb = false || sJustLoadedFromOldDb;
+
+        sJustLoadedFromOldDb = false;
+        if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
+
+            SharedPreferences.Editor editor = sp.edit();
+            editor.remove(UPGRADED_FROM_OLD_DATABASE);
+            editor.commit();
+            loadedOldDb = true;
+        }
+        return loadedOldDb;
+    }
+
+    /**
+     * @param workspaceResId that can be 0 to use default or non-zero for specific resource
+     */
+    synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
+
+        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+            int workspaceResId = origWorkspaceResId;
+
+            // Use default workspace resource if none provided
+            if (workspaceResId == 0) {
+                workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
+            }
+
+            // Populate favorites table with initial favorites
+            SharedPreferences.Editor editor = sp.edit();
+            editor.remove(EMPTY_DATABASE_CREATED);
+            if (origWorkspaceResId != 0) {
+                editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
+            }
+
+            mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
+            mOpenHelper.setFlagJustLoadedOldDb();
+            editor.commit();
+        }
+    }
+
+    private static interface ContentValuesCallback {
+        public void onRow(ContentValues values);
+    }
+
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+        private static final String TAG_FAVORITES = "favorites";
+        private static final String TAG_FAVORITE = "favorite";
+        private static final String TAG_CLOCK = "clock";
+        private static final String TAG_SEARCH = "search";
+        private static final String TAG_APPWIDGET = "appwidget";
+        private static final String TAG_SHORTCUT = "shortcut";
+        private static final String TAG_FOLDER = "folder";
+        private static final String TAG_EXTRA = "extra";
+        private static final String TAG_INCLUDE = "include";
+
+        private final Context mContext;
+        private final AppWidgetHost mAppWidgetHost;
+        private long mMaxItemId = -1;
+        private long mMaxScreenId = -1;
+
+        DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            mContext = context;
+            mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
+
+            // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
+            // the DB here
+            if (mMaxItemId == -1) {
+                mMaxItemId = initializeMaxItemId(getWritableDatabase());
+            }
+            if (mMaxScreenId == -1) {
+                mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
+            }
+        }
+
+        /**
+         * Send notification that we've deleted the {@link AppWidgetHost},
+         * probably as part of the initial database creation. The receiver may
+         * want to re-call {@link AppWidgetHost#startListening()} to ensure
+         * callbacks are correctly set.
+         */
+        private void sendAppWidgetResetNotify() {
+            final ContentResolver resolver = mContext.getContentResolver();
+            resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            if (LOGD) Log.d(TAG, "creating new launcher database");
+
+            mMaxItemId = 1;
+            mMaxScreenId = 0;
+
+            db.execSQL("CREATE TABLE favorites (" +
+                    "_id INTEGER PRIMARY KEY," +
+                    "title TEXT," +
+                    "intent TEXT," +
+                    "container INTEGER," +
+                    "screen INTEGER," +
+                    "cellX INTEGER," +
+                    "cellY INTEGER," +
+                    "spanX INTEGER," +
+                    "spanY INTEGER," +
+                    "itemType INTEGER," +
+                    "appWidgetId INTEGER NOT NULL DEFAULT -1," +
+                    "isShortcut INTEGER," +
+                    "iconType INTEGER," +
+                    "iconPackage TEXT," +
+                    "iconResource TEXT," +
+                    "icon BLOB," +
+                    "uri TEXT," +
+                    "displayMode INTEGER," +
+                    "appWidgetProvider TEXT," +
+                    "modified INTEGER NOT NULL DEFAULT 0" +
+                    ");");
+            addWorkspacesTable(db);
+
+            // Database was just created, so wipe any previous widgets
+            if (mAppWidgetHost != null) {
+                mAppWidgetHost.deleteHost();
+                sendAppWidgetResetNotify();
+            }
+
+            // Try converting the old database
+            ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
+                public void onRow(ContentValues values) {
+                    int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+                    if (container == Favorites.CONTAINER_DESKTOP) {
+                        int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+                        screen = (int) upgradeLauncherDb_permuteScreens(screen);
+                        values.put(LauncherSettings.Favorites.SCREEN, screen);
+                    }
+                }
+            };
+            Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
+                    "/old_favorites?notify=true");
+            if (!convertDatabase(db, uri, permuteScreensCb, true)) {
+                // Try and upgrade from the Launcher2 db
+                uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+                if (!convertDatabase(db, uri, permuteScreensCb, false)) {
+                    // If we fail, then set a flag to load the default workspace
+                    setFlagEmptyDbCreated();
+                    return;
+                }
+            }
+            // Right now, in non-default workspace cases, we want to run the final
+            // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
+            // set that flag too.
+            setFlagJustLoadedOldDb();
+        }
+
+        private void addWorkspacesTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
+                    LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
+                    LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
+                    LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
+                    ");");
+        }
+
+        private void setFlagJustLoadedOldDb() {
+            String spKey = LauncherAppState.getSharedPreferencesKey();
+            SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
+            editor.putBoolean(EMPTY_DATABASE_CREATED, false);
+            editor.commit();
+        }
+
+        private void setFlagEmptyDbCreated() {
+            String spKey = LauncherAppState.getSharedPreferencesKey();
+            SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putBoolean(EMPTY_DATABASE_CREATED, true);
+            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
+            editor.commit();
+        }
+
+        // We rearrange the screens from the old launcher
+        // 12345 -> 34512
+        private long upgradeLauncherDb_permuteScreens(long screen) {
+            if (screen >= 2) {
+                return screen - 2;
+            } else {
+                return screen + 3;
+            }
+        }
+
+        private boolean convertDatabase(SQLiteDatabase db, Uri uri,
+                                        ContentValuesCallback cb, boolean deleteRows) {
+            if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
+            boolean converted = false;
+
+            final ContentResolver resolver = mContext.getContentResolver();
+            Cursor cursor = null;
+
+            try {
+                cursor = resolver.query(uri, null, null, null, null);
+            } catch (Exception e) {
+                // Ignore
+            }
+
+            // We already have a favorites database in the old provider
+            if (cursor != null) {
+                try {
+                     if (cursor.getCount() > 0) {
+                        converted = copyFromCursor(db, cursor, cb) > 0;
+                        if (converted && deleteRows) {
+                            resolver.delete(uri, null, null);
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+
+            if (converted) {
+                // Convert widgets from this import into widgets
+                if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
+                convertWidgets(db);
+
+                // Update max item id
+                mMaxItemId = initializeMaxItemId(db);
+                if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
+            }
+
+            return converted;
+        }
+
+        private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
+            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+            final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+            final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
+            final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+            final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+            final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+            final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+            final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+            final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+            final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+            final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+            final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
+            final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
+
+            ContentValues[] rows = new ContentValues[c.getCount()];
+            int i = 0;
+            while (c.moveToNext()) {
+                ContentValues values = new ContentValues(c.getColumnCount());
+                values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
+                values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
+                values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
+                values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
+                values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
+                values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
+                values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
+                values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
+                values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
+                values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
+                values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
+                values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
+                values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
+                values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
+                values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
+                if (cb != null) {
+                    cb.onRow(values);
+                }
+                rows[i++] = values;
+            }
+
+            int total = 0;
+            if (i > 0) {
+                db.beginTransaction();
+                try {
+                    int numValues = rows.length;
+                    for (i = 0; i < numValues; i++) {
+                        if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
+                            return 0;
+                        } else {
+                            total++;
+                        }
+                    }
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            return total;
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
+
+            int version = oldVersion;
+            if (version < 3) {
+                // upgrade 1,2 -> 3 added appWidgetId column
+                db.beginTransaction();
+                try {
+                    // Insert new column for holding appWidgetIds
+                    db.execSQL("ALTER TABLE favorites " +
+                        "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
+                    db.setTransactionSuccessful();
+                    version = 3;
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+
+                // Convert existing widgets only if table upgrade was successful
+                if (version == 3) {
+                    convertWidgets(db);
+                }
+            }
+
+            if (version < 4) {
+                version = 4;
+            }
+
+            // Where's version 5?
+            // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
+            // - Passion shipped on 2.1 with version 6 of launcher3
+            // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
+            //   but version 5 on there was the updateContactsShortcuts change
+            //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
+            // The updateContactsShortcuts change is idempotent, so running it twice
+            // is okay so we'll do that when upgrading the devices that shipped with it.
+            if (version < 6) {
+                // We went from 3 to 5 screens. Move everything 1 to the right
+                db.beginTransaction();
+                try {
+                    db.execSQL("UPDATE favorites SET screen=(screen + 1);");
+                    db.setTransactionSuccessful();
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+
+               // We added the fast track.
+                if (updateContactsShortcuts(db)) {
+                    version = 6;
+                }
+            }
+
+            if (version < 7) {
+                // Version 7 gets rid of the special search widget.
+                convertWidgets(db);
+                version = 7;
+            }
+
+            if (version < 8) {
+                // Version 8 (froyo) has the icons all normalized.  This should
+                // already be the case in practice, but we now rely on it and don't
+                // resample the images each time.
+                normalizeIcons(db);
+                version = 8;
+            }
+
+            if (version < 9) {
+                // The max id is not yet set at this point (onUpgrade is triggered in the ctor
+                // before it gets a change to get set, so we need to read it here when we use it)
+                if (mMaxItemId == -1) {
+                    mMaxItemId = initializeMaxItemId(db);
+                }
+
+                // Add default hotseat icons
+                loadFavorites(db, R.xml.update_workspace);
+                version = 9;
+            }
+
+            // We bumped the version three time during JB, once to update the launch flags, once to
+            // update the override for the default launch animation and once to set the mimetype
+            // to improve startup performance
+            if (version < 12) {
+                // Contact shortcuts need a different set of flags to be launched now
+                // The updateContactsShortcuts change is idempotent, so we can keep using it like
+                // back in the Donut days
+                updateContactsShortcuts(db);
+                version = 12;
+            }
+
+            if (version < 13) {
+                // With the new shrink-wrapped and re-orderable workspaces, it makes sense
+                // to persist workspace screens and their relative order.
+                mMaxScreenId = 0;
+
+                // This will never happen in the wild, but when we switch to using workspace
+                // screen ids, redo the import from old launcher.
+                sJustLoadedFromOldDb = true;
+
+                addWorkspacesTable(db);
+                version = 13;
+            }
+
+            if (version < 14) {
+                db.beginTransaction();
+                try {
+                    // Insert new column for holding widget provider name
+                    db.execSQL("ALTER TABLE favorites " +
+                            "ADD COLUMN appWidgetProvider TEXT;");
+                    db.setTransactionSuccessful();
+                    version = 14;
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+
+            if (version < 15) {
+                db.beginTransaction();
+                try {
+                    // Insert new column for holding update timestamp
+                    db.execSQL("ALTER TABLE favorites " +
+                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+                    db.execSQL("ALTER TABLE workspaceScreens " +
+                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+                    db.setTransactionSuccessful();
+                    version = 15;
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            if (version != DATABASE_VERSION) {
+                Log.w(TAG, "Destroying all old data.");
+                db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
+                db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
+
+                onCreate(db);
+            }
+        }
+
+        private boolean updateContactsShortcuts(SQLiteDatabase db) {
+            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
+                    new int[] { Favorites.ITEM_TYPE_SHORTCUT });
+
+            Cursor c = null;
+            final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
+            db.beginTransaction();
+            try {
+                // Select and iterate through each matching widget
+                c = db.query(TABLE_FAVORITES,
+                        new String[] { Favorites._ID, Favorites.INTENT },
+                        selectWhere, null, null, null, null);
+                if (c == null) return false;
+
+                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
+
+                final int idIndex = c.getColumnIndex(Favorites._ID);
+                final int intentIndex = c.getColumnIndex(Favorites.INTENT);
+
+                while (c.moveToNext()) {
+                    long favoriteId = c.getLong(idIndex);
+                    final String intentUri = c.getString(intentIndex);
+                    if (intentUri != null) {
+                        try {
+                            final Intent intent = Intent.parseUri(intentUri, 0);
+                            android.util.Log.d("Home", intent.toString());
+                            final Uri uri = intent.getData();
+                            if (uri != null) {
+                                final String data = uri.toString();
+                                if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
+                                        actionQuickContact.equals(intent.getAction())) &&
+                                        (data.startsWith("content://contacts/people/") ||
+                                        data.startsWith("content://com.android.contacts/" +
+                                                "contacts/lookup/"))) {
+
+                                    final Intent newIntent = new Intent(actionQuickContact);
+                                    // When starting from the launcher, start in a new, cleared task
+                                    // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
+                                    // clear the whole thing preemptively here since
+                                    // QuickContactActivity will finish itself when launching other
+                                    // detail activities.
+                                    newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                                            Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                                    newIntent.putExtra(
+                                            Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
+                                    newIntent.setData(uri);
+                                    // Determine the type and also put that in the shortcut
+                                    // (that can speed up launch a bit)
+                                    newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
+
+                                    final ContentValues values = new ContentValues();
+                                    values.put(LauncherSettings.Favorites.INTENT,
+                                            newIntent.toUri(0));
+
+                                    String updateWhere = Favorites._ID + "=" + favoriteId;
+                                    db.update(TABLE_FAVORITES, values, updateWhere, null);
+                                }
+                            }
+                        } catch (RuntimeException ex) {
+                            Log.e(TAG, "Problem upgrading shortcut", ex);
+                        } catch (URISyntaxException e) {
+                            Log.e(TAG, "Problem upgrading shortcut", e);
+                        }
+                    }
+                }
+
+                db.setTransactionSuccessful();
+            } catch (SQLException ex) {
+                Log.w(TAG, "Problem while upgrading contacts", ex);
+                return false;
+            } finally {
+                db.endTransaction();
+                if (c != null) {
+                    c.close();
+                }
+            }
+
+            return true;
+        }
+
+        private void normalizeIcons(SQLiteDatabase db) {
+            Log.d(TAG, "normalizing icons");
+
+            db.beginTransaction();
+            Cursor c = null;
+            SQLiteStatement update = null;
+            try {
+                boolean logged = false;
+                update = db.compileStatement("UPDATE favorites "
+                        + "SET icon=? WHERE _id=?");
+
+                c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
+                        Favorites.ICON_TYPE_BITMAP, null);
+
+                final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
+                final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
+
+                while (c.moveToNext()) {
+                    long id = c.getLong(idIndex);
+                    byte[] data = c.getBlob(iconIndex);
+                    try {
+                        Bitmap bitmap = Utilities.resampleIconBitmap(
+                                BitmapFactory.decodeByteArray(data, 0, data.length),
+                                mContext);
+                        if (bitmap != null) {
+                            update.bindLong(1, id);
+                            data = ItemInfo.flattenBitmap(bitmap);
+                            if (data != null) {
+                                update.bindBlob(2, data);
+                                update.execute();
+                            }
+                            bitmap.recycle();
+                        }
+                    } catch (Exception e) {
+                        if (!logged) {
+                            Log.e(TAG, "Failed normalizing icon " + id, e);
+                        } else {
+                            Log.e(TAG, "Also failed normalizing icon " + id);
+                        }
+                        logged = true;
+                    }
+                }
+                db.setTransactionSuccessful();
+            } catch (SQLException ex) {
+                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
+            } finally {
+                db.endTransaction();
+                if (update != null) {
+                    update.close();
+                }
+                if (c != null) {
+                    c.close();
+                }
+            }
+        }
+
+        // Generates a new ID to use for an object in your database. This method should be only
+        // called from the main UI thread. As an exception, we do call it when we call the
+        // constructor from the worker thread; however, this doesn't extend until after the
+        // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
+        // after that point
+        public long generateNewItemId() {
+            if (mMaxItemId < 0) {
+                throw new RuntimeException("Error: max item id was not initialized");
+            }
+            mMaxItemId += 1;
+            return mMaxItemId;
+        }
+
+        public void updateMaxItemId(long id) {
+            mMaxItemId = id + 1;
+        }
+
+        private long initializeMaxItemId(SQLiteDatabase db) {
+            Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
+
+            // get the result
+            final int maxIdIndex = 0;
+            long id = -1;
+            if (c != null && c.moveToNext()) {
+                id = c.getLong(maxIdIndex);
+            }
+            if (c != null) {
+                c.close();
+            }
+
+            if (id == -1) {
+                throw new RuntimeException("Error: could not query max item id");
+            }
+
+            return id;
+        }
+
+        // Generates a new ID to use for an workspace screen in your database. This method
+        // should be only called from the main UI thread. As an exception, we do call it when we
+        // call the constructor from the worker thread; however, this doesn't extend until after the
+        // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
+        // after that point
+        public long generateNewScreenId() {
+            if (mMaxScreenId < 0) {
+                throw new RuntimeException("Error: max screen id was not initialized");
+            }
+            mMaxScreenId += 1;
+            return mMaxScreenId;
+        }
+
+        public void updateMaxScreenId(long maxScreenId) {
+            mMaxScreenId = maxScreenId;
+        }
+
+        private long initializeMaxScreenId(SQLiteDatabase db) {
+            Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
+
+            // get the result
+            final int maxIdIndex = 0;
+            long id = -1;
+            if (c != null && c.moveToNext()) {
+                id = c.getLong(maxIdIndex);
+            }
+            if (c != null) {
+                c.close();
+            }
+
+            if (id == -1) {
+                throw new RuntimeException("Error: could not query max screen id");
+            }
+
+            return id;
+        }
+
+        /**
+         * Upgrade existing clock and photo frame widgets into their new widget
+         * equivalents.
+         */
+        private void convertWidgets(SQLiteDatabase db) {
+            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+            final int[] bindSources = new int[] {
+                    Favorites.ITEM_TYPE_WIDGET_CLOCK,
+                    Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
+                    Favorites.ITEM_TYPE_WIDGET_SEARCH,
+            };
+
+            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
+
+            Cursor c = null;
+
+            db.beginTransaction();
+            try {
+                // Select and iterate through each matching widget
+                c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
+                        selectWhere, null, null, null, null);
+
+                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
+
+                final ContentValues values = new ContentValues();
+                while (c != null && c.moveToNext()) {
+                    long favoriteId = c.getLong(0);
+                    int favoriteType = c.getInt(1);
+
+                    // Allocate and update database with new appWidgetId
+                    try {
+                        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+                        if (LOGD) {
+                            Log.d(TAG, "allocated appWidgetId=" + appWidgetId
+                                    + " for favoriteId=" + favoriteId);
+                        }
+                        values.clear();
+                        values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
+                        values.put(Favorites.APPWIDGET_ID, appWidgetId);
+
+                        // Original widgets might not have valid spans when upgrading
+                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
+                            values.put(LauncherSettings.Favorites.SPANX, 4);
+                            values.put(LauncherSettings.Favorites.SPANY, 1);
+                        } else {
+                            values.put(LauncherSettings.Favorites.SPANX, 2);
+                            values.put(LauncherSettings.Favorites.SPANY, 2);
+                        }
+
+                        String updateWhere = Favorites._ID + "=" + favoriteId;
+                        db.update(TABLE_FAVORITES, values, updateWhere, null);
+
+                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
+                            // TODO: check return value
+                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+                                    new ComponentName("com.android.alarmclock",
+                                    "com.android.alarmclock.AnalogAppWidgetProvider"));
+                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
+                            // TODO: check return value
+                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+                                    new ComponentName("com.android.camera",
+                                    "com.android.camera.PhotoAppWidgetProvider"));
+                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
+                            // TODO: check return value
+                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+                                    getSearchWidgetProvider());
+                        }
+                    } catch (RuntimeException ex) {
+                        Log.e(TAG, "Problem allocating appWidgetId", ex);
+                    }
+                }
+
+                db.setTransactionSuccessful();
+            } catch (SQLException ex) {
+                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
+            } finally {
+                db.endTransaction();
+                if (c != null) {
+                    c.close();
+                }
+            }
+
+            // Update max item id
+            mMaxItemId = initializeMaxItemId(db);
+            if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
+        }
+
+        private static final void beginDocument(XmlPullParser parser, String firstElementName)
+                throws XmlPullParserException, IOException {
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new XmlPullParserException("No start tag found");
+            }
+
+            if (!parser.getName().equals(firstElementName)) {
+                throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+                        ", expected " + firstElementName);
+            }
+        }
+
+        /**
+         * Loads the default set of favorite packages from an xml file.
+         *
+         * @param db The database to write the values into
+         * @param filterContainerId The specific container id of items to load
+         */
+        private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
+            Intent intent = new Intent(Intent.ACTION_MAIN, null);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+            ContentValues values = new ContentValues();
+
+            if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
+
+            PackageManager packageManager = mContext.getPackageManager();
+            int i = 0;
+            try {
+                XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
+                AttributeSet attrs = Xml.asAttributeSet(parser);
+                beginDocument(parser, TAG_FAVORITES);
+
+                final int depth = parser.getDepth();
+
+                int type;
+                while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+
+                    boolean added = false;
+                    final String name = parser.getName();
+
+                    if (TAG_INCLUDE.equals(name)) {
+                        final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include);
+
+                        final int resId = a.getResourceId(R.styleable.Include_workspace, 0);
+
+                        if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
+                                "", resId));
+
+                        if (resId != 0 && resId != workspaceResourceId) {
+                            // recursively load some more favorites, why not?
+                            i += loadFavorites(db, resId);
+                            added = false;
+                            mMaxItemId = -1;
+                        } else {
+                            Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
+                        }
+
+                        a.recycle();
+
+                        if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
+                        continue;
+                    }
+
+                    // Assuming it's a <favorite> at this point
+                    TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
+
+                    long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+                    if (a.hasValue(R.styleable.Favorite_container)) {
+                        container = Long.valueOf(a.getString(R.styleable.Favorite_container));
+                    }
+
+                    String screen = a.getString(R.styleable.Favorite_screen);
+                    String x = a.getString(R.styleable.Favorite_x);
+                    String y = a.getString(R.styleable.Favorite_y);
+
+                    values.clear();
+                    values.put(LauncherSettings.Favorites.CONTAINER, container);
+                    values.put(LauncherSettings.Favorites.SCREEN, screen);
+                    values.put(LauncherSettings.Favorites.CELLX, x);
+                    values.put(LauncherSettings.Favorites.CELLY, y);
+
+                    if (LOGD) {
+                        final String title = a.getString(R.styleable.Favorite_title);
+                        final String pkg = a.getString(R.styleable.Favorite_packageName);
+                        final String something = title != null ? title : pkg;
+                        Log.v(TAG, String.format(
+                                ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
+                                "", name,
+                                (something == null ? "" : (" \"" + something + "\"")),
+                                container, screen, x, y));
+                    }
+
+                    if (TAG_FAVORITE.equals(name)) {
+                        long id = addAppShortcut(db, values, a, packageManager, intent);
+                        added = id >= 0;
+                    } else if (TAG_SEARCH.equals(name)) {
+                        added = addSearchWidget(db, values);
+                    } else if (TAG_CLOCK.equals(name)) {
+                        added = addClockWidget(db, values);
+                    } else if (TAG_APPWIDGET.equals(name)) {
+                        added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
+                    } else if (TAG_SHORTCUT.equals(name)) {
+                        long id = addUriShortcut(db, values, a);
+                        added = id >= 0;
+                    } else if (TAG_FOLDER.equals(name)) {
+                        String title;
+                        int titleResId =  a.getResourceId(R.styleable.Favorite_title, -1);
+                        if (titleResId != -1) {
+                            title = mContext.getResources().getString(titleResId);
+                        } else {
+                            title = mContext.getResources().getString(R.string.folder_name);
+                        }
+                        values.put(LauncherSettings.Favorites.TITLE, title);
+                        long folderId = addFolder(db, values);
+                        added = folderId >= 0;
+
+                        ArrayList<Long> folderItems = new ArrayList<Long>();
+
+                        int folderDepth = parser.getDepth();
+                        while ((type = parser.next()) != XmlPullParser.END_TAG ||
+                                parser.getDepth() > folderDepth) {
+                            if (type != XmlPullParser.START_TAG) {
+                                continue;
+                            }
+                            final String folder_item_name = parser.getName();
+
+                            TypedArray ar = mContext.obtainStyledAttributes(attrs,
+                                    R.styleable.Favorite);
+                            values.clear();
+                            values.put(LauncherSettings.Favorites.CONTAINER, folderId);
+
+                            if (LOGD) {
+                                final String pkg = ar.getString(R.styleable.Favorite_packageName);
+                                final String uri = ar.getString(R.styleable.Favorite_uri);
+                                Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
+                                        folder_item_name, uri != null ? uri : pkg));
+                            }
+
+                            if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
+                                long id =
+                                    addAppShortcut(db, values, ar, packageManager, intent);
+                                if (id >= 0) {
+                                    folderItems.add(id);
+                                }
+                            } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
+                                long id = addUriShortcut(db, values, ar);
+                                if (id >= 0) {
+                                    folderItems.add(id);
+                                }
+                            } else {
+                                throw new RuntimeException("Folders can " +
+                                        "contain only shortcuts");
+                            }
+                            ar.recycle();
+                        }
+                        // We can only have folders with >= 2 items, so we need to remove the
+                        // folder and clean up if less than 2 items were included, or some
+                        // failed to add, and less than 2 were actually added
+                        if (folderItems.size() < 2 && folderId >= 0) {
+                            // We just delete the folder and any items that made it
+                            deleteId(db, folderId);
+                            if (folderItems.size() > 0) {
+                                deleteId(db, folderItems.get(0));
+                            }
+                            added = false;
+                        }
+                    }
+                    if (added) i++;
+                    a.recycle();
+                }
+            } catch (XmlPullParserException e) {
+                Log.w(TAG, "Got exception parsing favorites.", e);
+            } catch (IOException e) {
+                Log.w(TAG, "Got exception parsing favorites.", e);
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Got exception parsing favorites.", e);
+            }
+
+            // Update the max item id after we have loaded the database
+            if (mMaxItemId == -1) {
+                mMaxItemId = initializeMaxItemId(db);
+            }
+
+            return i;
+        }
+
+        private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
+                PackageManager packageManager, Intent intent) {
+            long id = -1;
+            ActivityInfo info;
+            String packageName = a.getString(R.styleable.Favorite_packageName);
+            String className = a.getString(R.styleable.Favorite_className);
+            try {
+                ComponentName cn;
+                try {
+                    cn = new ComponentName(packageName, className);
+                    info = packageManager.getActivityInfo(cn, 0);
+                } catch (PackageManager.NameNotFoundException nnfe) {
+                    String[] packages = packageManager.currentToCanonicalPackageNames(
+                        new String[] { packageName });
+                    cn = new ComponentName(packages[0], className);
+                    info = packageManager.getActivityInfo(cn, 0);
+                }
+                id = generateNewItemId();
+                intent.setComponent(cn);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                values.put(Favorites.INTENT, intent.toUri(0));
+                values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
+                values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
+                values.put(Favorites.SPANX, 1);
+                values.put(Favorites.SPANY, 1);
+                values.put(Favorites._ID, generateNewItemId());
+                if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
+                    return -1;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "Unable to add favorite: " + packageName +
+                        "/" + className, e);
+            }
+            return id;
+        }
+
+        private long addFolder(SQLiteDatabase db, ContentValues values) {
+            values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
+            values.put(Favorites.SPANX, 1);
+            values.put(Favorites.SPANY, 1);
+            long id = generateNewItemId();
+            values.put(Favorites._ID, id);
+            if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
+                return -1;
+            } else {
+                return id;
+            }
+        }
+
+        private ComponentName getSearchWidgetProvider() {
+            SearchManager searchManager =
+                    (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+            ComponentName searchComponent = searchManager.getGlobalSearchActivity();
+            if (searchComponent == null) return null;
+            return getProviderInPackage(searchComponent.getPackageName());
+        }
+
+        /**
+         * Gets an appwidget provider from the given package. If the package contains more than
+         * one appwidget provider, an arbitrary one is returned.
+         */
+        private ComponentName getProviderInPackage(String packageName) {
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+            List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
+            if (providers == null) return null;
+            final int providerCount = providers.size();
+            for (int i = 0; i < providerCount; i++) {
+                ComponentName provider = providers.get(i).provider;
+                if (provider != null && provider.getPackageName().equals(packageName)) {
+                    return provider;
+                }
+            }
+            return null;
+        }
+
+        private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
+            ComponentName cn = getSearchWidgetProvider();
+            return addAppWidget(db, values, cn, 4, 1, null);
+        }
+
+        private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
+            ComponentName cn = new ComponentName("com.android.alarmclock",
+                    "com.android.alarmclock.AnalogAppWidgetProvider");
+            return addAppWidget(db, values, cn, 2, 2, null);
+        }
+
+        private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
+                SQLiteDatabase db, ContentValues values, TypedArray a,
+                PackageManager packageManager) throws XmlPullParserException, IOException {
+
+            String packageName = a.getString(R.styleable.Favorite_packageName);
+            String className = a.getString(R.styleable.Favorite_className);
+
+            if (packageName == null || className == null) {
+                return false;
+            }
+
+            boolean hasPackage = true;
+            ComponentName cn = new ComponentName(packageName, className);
+            try {
+                packageManager.getReceiverInfo(cn, 0);
+            } catch (Exception e) {
+                String[] packages = packageManager.currentToCanonicalPackageNames(
+                        new String[] { packageName });
+                cn = new ComponentName(packages[0], className);
+                try {
+                    packageManager.getReceiverInfo(cn, 0);
+                } catch (Exception e1) {
+                    hasPackage = false;
+                }
+            }
+
+            if (hasPackage) {
+                int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
+                int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
+
+                // Read the extras
+                Bundle extras = new Bundle();
+                int widgetDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_TAG ||
+                        parser.getDepth() > widgetDepth) {
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+
+                    TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
+                    if (TAG_EXTRA.equals(parser.getName())) {
+                        String key = ar.getString(R.styleable.Extra_key);
+                        String value = ar.getString(R.styleable.Extra_value);
+                        if (key != null && value != null) {
+                            extras.putString(key, value);
+                        } else {
+                            throw new RuntimeException("Widget extras must have a key and value");
+                        }
+                    } else {
+                        throw new RuntimeException("Widgets can contain only extras");
+                    }
+                    ar.recycle();
+                }
+
+                return addAppWidget(db, values, cn, spanX, spanY, extras);
+            }
+
+            return false;
+        }
+        private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
+                int spanX, int spanY, Bundle extras) {
+            boolean allocatedAppWidgets = false;
+            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+
+            try {
+                int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+                values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
+                values.put(Favorites.SPANX, spanX);
+                values.put(Favorites.SPANY, spanY);
+                values.put(Favorites.APPWIDGET_ID, appWidgetId);
+                values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
+                values.put(Favorites._ID, generateNewItemId());
+                dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
+
+                allocatedAppWidgets = true;
+
+                // TODO: need to check return value
+                appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
+
+                // Send a broadcast to configure the widget
+                if (extras != null && !extras.isEmpty()) {
+                    Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
+                    intent.setComponent(cn);
+                    intent.putExtras(extras);
+                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+                    mContext.sendBroadcast(intent);
+                }
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Problem allocating appWidgetId", ex);
+            }
+
+            return allocatedAppWidgets;
+        }
+
+        private long addUriShortcut(SQLiteDatabase db, ContentValues values,
+                TypedArray a) {
+            Resources r = mContext.getResources();
+
+            final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
+            final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
+
+            Intent intent;
+            String uri = null;
+            try {
+                uri = a.getString(R.styleable.Favorite_uri);
+                intent = Intent.parseUri(uri, 0);
+            } catch (URISyntaxException e) {
+                Log.w(TAG, "Shortcut has malformed uri: " + uri);
+                return -1; // Oh well
+            }
+
+            if (iconResId == 0 || titleResId == 0) {
+                Log.w(TAG, "Shortcut is missing title or icon resource ID");
+                return -1;
+            }
+
+            long id = generateNewItemId();
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            values.put(Favorites.INTENT, intent.toUri(0));
+            values.put(Favorites.TITLE, r.getString(titleResId));
+            values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
+            values.put(Favorites.SPANX, 1);
+            values.put(Favorites.SPANY, 1);
+            values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
+            values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
+            values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
+            values.put(Favorites._ID, id);
+
+            if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
+                return -1;
+            }
+            return id;
+        }
+    }
+
+    /**
+     * Build a query string that will match any row where the column matches
+     * anything in the values list.
+     */
+    static String buildOrWhereString(String column, int[] values) {
+        StringBuilder selectWhere = new StringBuilder();
+        for (int i = values.length - 1; i >= 0; i--) {
+            selectWhere.append(column).append("=").append(values[i]);
+            if (i > 0) {
+                selectWhere.append(" OR ");
+            }
+        }
+        return selectWhere.toString();
+    }
+
+    static class SqlArguments {
+        public final String table;
+        public final String where;
+        public final String[] args;
+
+        SqlArguments(Uri url, String where, String[] args) {
+            if (url.getPathSegments().size() == 1) {
+                this.table = url.getPathSegments().get(0);
+                this.where = where;
+                this.args = args;
+            } else if (url.getPathSegments().size() != 2) {
+                throw new IllegalArgumentException("Invalid URI: " + url);
+            } else if (!TextUtils.isEmpty(where)) {
+                throw new UnsupportedOperationException("WHERE clause not supported: " + url);
+            } else {
+                this.table = url.getPathSegments().get(0);
+                this.where = "_id=" + ContentUris.parseId(url);
+                this.args = null;
+            }
+        }
+
+        SqlArguments(Uri url) {
+            if (url.getPathSegments().size() == 1) {
+                table = url.getPathSegments().get(0);
+                where = null;
+                args = null;
+            } else {
+                throw new IllegalArgumentException("Invalid URI: " + url);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
new file mode 100644
index 0000000..988e5ef
--- /dev/null
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+/**
+ * Settings related utilities.
+ */
+class LauncherSettings {
+    /** Columns required on table staht will be subject to backup and restore. */
+    static interface ChangeLogColumns extends BaseColumns {
+        /**
+         * The time of the last update to this row.
+         * <P>Type: INTEGER</P>
+         */
+        static final String MODIFIED = "modified";
+    }
+
+    static interface BaseLauncherColumns extends ChangeLogColumns {
+        /**
+         * Descriptive name of the gesture that can be displayed to the user.
+         * <P>Type: TEXT</P>
+         */
+        static final String TITLE = "title";
+
+        /**
+         * The Intent URL of the gesture, describing what it points to. This
+         * value is given to {@link android.content.Intent#parseUri(String, int)} to create
+         * an Intent that can be launched.
+         * <P>Type: TEXT</P>
+         */
+        static final String INTENT = "intent";
+
+        /**
+         * The type of the gesture
+         *
+         * <P>Type: INTEGER</P>
+         */
+        static final String ITEM_TYPE = "itemType";
+
+        /**
+         * The gesture is an application
+         */
+        static final int ITEM_TYPE_APPLICATION = 0;
+
+        /**
+         * The gesture is an application created shortcut
+         */
+        static final int ITEM_TYPE_SHORTCUT = 1;
+
+        /**
+         * The icon type.
+         * <P>Type: INTEGER</P>
+         */
+        static final String ICON_TYPE = "iconType";
+
+        /**
+         * The icon is a resource identified by a package name and an integer id.
+         */
+        static final int ICON_TYPE_RESOURCE = 0;
+
+        /**
+         * The icon is a bitmap.
+         */
+        static final int ICON_TYPE_BITMAP = 1;
+
+        /**
+         * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        static final String ICON_PACKAGE = "iconPackage";
+
+        /**
+         * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        static final String ICON_RESOURCE = "iconResource";
+
+        /**
+         * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+         * <P>Type: BLOB</P>
+         */
+        static final String ICON = "icon";
+    }
+
+    /**
+     * Workspace Screens.
+     *
+     * Tracks the order of workspace screens.
+     */
+    static final class WorkspaceScreens implements ChangeLogColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        static final Uri CONTENT_URI = Uri.parse("content://" +
+                LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_WORKSPACE_SCREENS +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+        /**
+         * The rank of this screen -- ie. how it is ordered relative to the other screens.
+         * <P>Type: INTEGER</P>
+         */
+        static final String SCREEN_RANK = "screenRank";
+    }
+
+    /**
+     * Favorites.
+     */
+    static final class Favorites implements BaseLauncherColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        static final Uri CONTENT_URI = Uri.parse("content://" +
+                LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+        /**
+         * The content:// style URL for this table
+         */
+        static final Uri OLD_CONTENT_URI = Uri.parse("content://" +
+                LauncherProvider.OLD_AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+        /**
+         * The content:// style URL for this table. When this Uri is used, no notification is
+         * sent if the content changes.
+         */
+        static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
+                LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
+
+        /**
+         * The content:// style URL for a given row, identified by its id.
+         *
+         * @param id The row id.
+         * @param notify True to send a notification is the content changes.
+         *
+         * @return The unique content URL for the specified row.
+         */
+        static Uri getContentUri(long id, boolean notify) {
+            return Uri.parse("content://" + LauncherProvider.AUTHORITY +
+                    "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" +
+                    LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
+        }
+
+        /**
+         * The container holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        static final String CONTAINER = "container";
+
+        /**
+         * The icon is a resource identified by a package name and an integer id.
+         */
+        static final int CONTAINER_DESKTOP = -100;
+        static final int CONTAINER_HOTSEAT = -101;
+
+        /**
+         * The screen holding the favorite (if container is CONTAINER_DESKTOP)
+         * <P>Type: INTEGER</P>
+         */
+        static final String SCREEN = "screen";
+
+        /**
+         * The X coordinate of the cell holding the favorite
+         * (if container is CONTAINER_HOTSEAT or CONTAINER_HOTSEAT)
+         * <P>Type: INTEGER</P>
+         */
+        static final String CELLX = "cellX";
+
+        /**
+         * The Y coordinate of the cell holding the favorite
+         * (if container is CONTAINER_DESKTOP)
+         * <P>Type: INTEGER</P>
+         */
+        static final String CELLY = "cellY";
+
+        /**
+         * The X span of the cell holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        static final String SPANX = "spanX";
+
+        /**
+         * The Y span of the cell holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        static final String SPANY = "spanY";
+
+        /**
+         * The favorite is a user created folder
+         */
+        static final int ITEM_TYPE_FOLDER = 2;
+
+        /**
+        * The favorite is a live folder
+        *
+        * Note: live folders can no longer be added to Launcher, and any live folders which
+        * exist within the launcher database will be ignored when loading.  That said, these
+        * entries in the database may still exist, and are not automatically stripped.
+        */
+        static final int ITEM_TYPE_LIVE_FOLDER = 3;
+
+        /**
+         * The favorite is a widget
+         */
+        static final int ITEM_TYPE_APPWIDGET = 4;
+
+        /**
+         * The favorite is a clock
+         */
+        static final int ITEM_TYPE_WIDGET_CLOCK = 1000;
+
+        /**
+         * The favorite is a search widget
+         */
+        static final int ITEM_TYPE_WIDGET_SEARCH = 1001;
+
+        /**
+         * The favorite is a photo frame
+         */
+        static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002;
+
+        /**
+         * The appWidgetId of the widget
+         *
+         * <P>Type: INTEGER</P>
+         */
+        static final String APPWIDGET_ID = "appWidgetId";
+
+        /**
+         * The ComponentName of the widget provider
+         *
+         * <P>Type: STRING</P>
+         */
+        public static final String APPWIDGET_PROVIDER = "appWidgetProvider";
+        
+        /**
+         * Indicates whether this favorite is an application-created shortcut or not.
+         * If the value is 0, the favorite is not an application-created shortcut, if the
+         * value is 1, it is an application-created shortcut.
+         * <P>Type: INTEGER</P>
+         */
+        @Deprecated
+        static final String IS_SHORTCUT = "isShortcut";
+
+        /**
+         * The URI associated with the favorite. It is used, for instance, by
+         * live folders to find the content provider.
+         * <P>Type: TEXT</P>
+         */
+        static final String URI = "uri";
+
+        /**
+         * The display mode if the item is a live folder.
+         * <P>Type: INTEGER</P>
+         *
+         * @see android.provider.LiveFolders#DISPLAY_MODE_GRID
+         * @see android.provider.LiveFolders#DISPLAY_MODE_LIST
+         */
+        static final String DISPLAY_MODE = "displayMode";
+    }
+}
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
new file mode 100644
index 0000000..4cafbbf
--- /dev/null
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -0,0 +1,275 @@
+/*
+ * 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.TimeInterpolator;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+    enum Properties {
+            TRANSLATION_X,
+            TRANSLATION_Y,
+            SCALE_X,
+            SCALE_Y,
+            ROTATION_Y,
+            ALPHA,
+            START_DELAY,
+            DURATION,
+            INTERPOLATOR,
+            WITH_LAYER
+    }
+    EnumSet<Properties> mPropertiesToSet = EnumSet.noneOf(Properties.class);
+    ViewPropertyAnimator mViewPropertyAnimator;
+    View mTarget;
+
+    float mTranslationX;
+    float mTranslationY;
+    float mScaleX;
+    float mScaleY;
+    float mRotationY;
+    float mAlpha;
+    long mStartDelay;
+    long mDuration;
+    TimeInterpolator mInterpolator;
+    ArrayList<Animator.AnimatorListener> mListeners;
+    boolean mRunning = false;
+    FirstFrameAnimatorHelper mFirstFrameHelper;
+
+    public LauncherViewPropertyAnimator(View target) {
+        mTarget = target;
+        mListeners = new ArrayList<Animator.AnimatorListener>();
+    }
+
+    @Override
+    public void addListener(Animator.AnimatorListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void cancel() {
+        if (mViewPropertyAnimator != null) {
+            mViewPropertyAnimator.cancel();
+        }
+    }
+
+    @Override
+    public Animator clone() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public void end() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public long getDuration() {
+        return mDuration;
+    }
+
+    @Override
+    public ArrayList<Animator.AnimatorListener> getListeners() {
+        return mListeners;
+    }
+
+    @Override
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            Animator.AnimatorListener listener = mListeners.get(i);
+            listener.onAnimationCancel(this);
+        }
+        mRunning = false;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            Animator.AnimatorListener listener = mListeners.get(i);
+            listener.onAnimationEnd(this);
+        }
+        mRunning = false;
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            Animator.AnimatorListener listener = mListeners.get(i);
+            listener.onAnimationRepeat(this);
+        }
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+        // This is the first time we get a handle to the internal ValueAnimator
+        // used by the ViewPropertyAnimator.
+        mFirstFrameHelper.onAnimationStart(animation);
+
+        for (int i = 0; i < mListeners.size(); i++) {
+            Animator.AnimatorListener listener = mListeners.get(i);
+            listener.onAnimationStart(this);
+        }
+        mRunning = true;
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mRunning;
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mViewPropertyAnimator != null;
+    }
+
+    @Override
+    public void removeAllListeners() {
+        mListeners.clear();
+    }
+
+    @Override
+    public void removeListener(Animator.AnimatorListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public Animator setDuration(long duration) {
+        mPropertiesToSet.add(Properties.DURATION);
+        mDuration = duration;
+        return this;
+    }
+
+    @Override
+    public void setInterpolator(TimeInterpolator value) {
+        mPropertiesToSet.add(Properties.INTERPOLATOR);
+        mInterpolator = value;
+    }
+
+    @Override
+    public void setStartDelay(long startDelay) {
+        mPropertiesToSet.add(Properties.START_DELAY);
+        mStartDelay = startDelay;
+    }
+
+    @Override
+    public void setTarget(Object target) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public void setupEndValues() {
+
+    }
+
+    @Override
+    public void setupStartValues() {
+    }
+
+    @Override
+    public void start() {
+        mViewPropertyAnimator = mTarget.animate();
+
+        // FirstFrameAnimatorHelper hooks itself up to the updates on the animator,
+        // and then adjusts the play time to keep the first two frames jank-free
+        mFirstFrameHelper = new FirstFrameAnimatorHelper(mViewPropertyAnimator, mTarget);
+
+        if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) {
+            mViewPropertyAnimator.translationX(mTranslationX);
+        }
+        if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) {
+            mViewPropertyAnimator.translationY(mTranslationY);
+        }
+        if (mPropertiesToSet.contains(Properties.SCALE_X)) {
+            mViewPropertyAnimator.scaleX(mScaleX);
+        }
+        if (mPropertiesToSet.contains(Properties.ROTATION_Y)) {
+            mViewPropertyAnimator.rotationY(mRotationY);
+        }
+        if (mPropertiesToSet.contains(Properties.SCALE_Y)) {
+            mViewPropertyAnimator.scaleY(mScaleY);
+        }
+        if (mPropertiesToSet.contains(Properties.ALPHA)) {
+            mViewPropertyAnimator.alpha(mAlpha);
+        }
+        if (mPropertiesToSet.contains(Properties.START_DELAY)) {
+            mViewPropertyAnimator.setStartDelay(mStartDelay);
+        }
+        if (mPropertiesToSet.contains(Properties.DURATION)) {
+            mViewPropertyAnimator.setDuration(mDuration);
+        }
+        if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) {
+            mViewPropertyAnimator.setInterpolator(mInterpolator);
+        }
+        if (mPropertiesToSet.contains(Properties.WITH_LAYER)) {
+            mViewPropertyAnimator.withLayer();
+        }
+        mViewPropertyAnimator.setListener(this);
+        mViewPropertyAnimator.start();
+        LauncherAnimUtils.cancelOnDestroyActivity(this);
+    }
+
+    public LauncherViewPropertyAnimator translationX(float value) {
+        mPropertiesToSet.add(Properties.TRANSLATION_X);
+        mTranslationX = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator translationY(float value) {
+        mPropertiesToSet.add(Properties.TRANSLATION_Y);
+        mTranslationY = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator scaleX(float value) {
+        mPropertiesToSet.add(Properties.SCALE_X);
+        mScaleX = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator scaleY(float value) {
+        mPropertiesToSet.add(Properties.SCALE_Y);
+        mScaleY = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator rotationY(float value) {
+        mPropertiesToSet.add(Properties.ROTATION_Y);
+        mRotationY = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator alpha(float value) {
+        mPropertiesToSet.add(Properties.ALPHA);
+        mAlpha = value;
+        return this;
+    }
+
+    public LauncherViewPropertyAnimator withLayer() {
+        mPropertiesToSet.add(Properties.WITH_LAYER);
+        return this;
+    }
+}
diff --git a/src/com/android/launcher3/LiveWallpaperListAdapter.java b/src/com/android/launcher3/LiveWallpaperListAdapter.java
new file mode 100644
index 0000000..54e0af7
--- /dev/null
+++ b/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -0,0 +1,203 @@
+/*
+ * 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.launcher3;
+
+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.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+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 LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter {
+    private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+    private final LayoutInflater mInflater;
+    private final PackageManager mPackageManager;
+
+    private List<LiveWallpaperTile> mWallpapers;
+
+    @SuppressWarnings("unchecked")
+    public LiveWallpaperListAdapter(Context context) {
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPackageManager = context.getPackageManager();
+
+        List<ResolveInfo> list = mPackageManager.queryIntentServices(
+                new Intent(WallpaperService.SERVICE_INTERFACE),
+                PackageManager.GET_META_DATA);
+
+        mWallpapers = new ArrayList<LiveWallpaperTile>();
+
+        new LiveWallpaperEnumerator(context).execute(list);
+    }
+
+    public int getCount() {
+        if (mWallpapers == null) {
+            return 0;
+        }
+        return mWallpapers.size();
+    }
+
+    public LiveWallpaperTile getItem(int position) {
+        return mWallpapers.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view;
+
+        if (convertView == null) {
+            view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+        LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
+        wallpaperInfo.setView(view);
+        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+        ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
+        if (wallpaperInfo.mThumbnail != null) {
+            image.setImageDrawable(wallpaperInfo.mThumbnail);
+            icon.setVisibility(View.GONE);
+        } else {
+            icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
+            icon.setVisibility(View.VISIBLE);
+        }
+
+        TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+        label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
+
+        return view;
+    }
+
+    public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private Drawable mThumbnail;
+        private WallpaperInfo mInfo;
+        public LiveWallpaperTile(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.onLiveWallpaperPickerLaunch();
+            Utilities.startActivityForResultSafely(
+                    a, preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
+        }
+    }
+
+    private class LiveWallpaperEnumerator extends
+            AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
+        private Context mContext;
+        private int mWallpaperPosition;
+
+        public LiveWallpaperEnumerator(Context context) {
+            super();
+            mContext = context;
+            mWallpaperPosition = 0;
+        }
+
+        @Override
+        protected Void doInBackground(List<ResolveInfo>... params) {
+            final PackageManager packageManager = mContext.getPackageManager();
+
+            List<ResolveInfo> list = params[0];
+
+            Collections.sort(list, new Comparator<ResolveInfo>() {
+                final Collator mCollator;
+
+                {
+                    mCollator = Collator.getInstance();
+                }
+
+                public int compare(ResolveInfo info1, ResolveInfo info2) {
+                    return mCollator.compare(info1.loadLabel(packageManager),
+                            info2.loadLabel(packageManager));
+                }
+            });
+
+            for (ResolveInfo resolveInfo : list) {
+                WallpaperInfo info = null;
+                try {
+                    info = new WallpaperInfo(mContext, resolveInfo);
+                } catch (XmlPullParserException e) {
+                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                    continue;
+                } catch (IOException e) {
+                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                    continue;
+                }
+
+
+                Drawable thumb = info.loadThumbnail(packageManager);
+                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+                LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
+                publishProgress(wallpaper);
+            }
+            // Send a null object to show loading is finished
+            publishProgress((LiveWallpaperTile) null);
+
+            return null;
+        }
+
+        @Override
+        protected void onProgressUpdate(LiveWallpaperTile...infos) {
+            for (LiveWallpaperTile info : infos) {
+                if (info == null) {
+                    LiveWallpaperListAdapter.this.notifyDataSetChanged();
+                    break;
+                }
+                info.mThumbnail.setDither(true);
+                if (mWallpaperPosition < mWallpapers.size()) {
+                    mWallpapers.set(mWallpaperPosition, info);
+                } else {
+                    mWallpapers.add(info);
+                }
+                mWallpaperPosition++;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/MemoryDumpActivity.java b/src/com/android/launcher3/MemoryDumpActivity.java
new file mode 100644
index 0000000..d79be80
--- /dev/null
+++ b/src/com/android/launcher3/MemoryDumpActivity.java
@@ -0,0 +1,173 @@
+/*
+ * 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.launcher3;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.*;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class MemoryDumpActivity extends Activity {
+    private static final String TAG = "MemoryDumpActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    public static String zipUp(ArrayList<String> paths) {
+        final int BUFSIZ = 256 * 1024; // 256K
+        final byte[] buf = new byte[BUFSIZ];
+        final String zipfilePath = String.format("%s/hprof-%d.zip",
+                Environment.getExternalStorageDirectory(),
+                System.currentTimeMillis());
+        ZipOutputStream zos = null;
+        try {
+            OutputStream os = new FileOutputStream(zipfilePath);
+            zos = new ZipOutputStream(new BufferedOutputStream(os));
+            for (String filename : paths) {
+                InputStream is = null;
+                try {
+                    is = new BufferedInputStream(new FileInputStream(filename));
+                    ZipEntry entry = new ZipEntry(filename);
+                    zos.putNextEntry(entry);
+                    int len;
+                    while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
+                        zos.write(buf, 0, len);
+                    }
+                    zos.closeEntry();
+                } finally {
+                    is.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "error zipping up profile data", e);
+            return null;
+        } finally {
+            if (zos != null) {
+                try {
+                    zos.close();
+                } catch (IOException e) {
+                    // ugh, whatever
+                }
+            }
+        }
+        return zipfilePath;
+    }
+
+    public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
+        final StringBuilder body = new StringBuilder();
+
+        final ArrayList<String> paths = new ArrayList<String>();
+        final int myPid = android.os.Process.myPid();
+
+        final int[] pids_orig = tracker.getTrackedProcesses();
+        final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length);
+        for (int pid : pids_copy) {
+            MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
+            if (info != null) {
+                body.append("pid ").append(pid).append(":")
+                    .append(" up=").append(info.getUptime())
+                    .append(" pss=").append(info.currentPss)
+                    .append(" uss=").append(info.currentUss)
+                    .append("\n");
+            }
+            if (pid == myPid) {
+                final String path = String.format("%s/launcher-memory-%d.ahprof",
+                        Environment.getExternalStorageDirectory(),
+                        pid);
+                Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
+                try {
+                    android.os.Debug.dumpHprofData(path); // will block
+                } catch (IOException e) {
+                    Log.e(TAG, "error dumping memory:", e);
+                }
+                paths.add(path);
+            }
+        }
+
+        String zipfile = zipUp(paths);
+
+        if (zipfile == null) return;
+
+        Intent shareIntent = new Intent(Intent.ACTION_SEND);
+        shareIntent.setType("application/zip");
+
+        final PackageManager pm = context.getPackageManager();
+        shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid));
+        String appVersion;
+        try {
+            appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            appVersion = "?";
+        }
+
+        body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
+        shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
+
+        final File pathFile = new File(zipfile);
+        final Uri pathUri = Uri.fromFile(pathFile);
+
+        shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
+        context.startActivity(shareIntent);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        startDump(this, new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        });
+    }
+
+    public static void startDump(final Context context) {
+        startDump(context, null);
+    }
+
+    public static void startDump(final Context context, final Runnable andThen) {
+        final ServiceConnection connection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                Log.v(TAG, "service connected, dumping...");
+                dumpHprofAndShare(context,
+                        ((MemoryTracker.MemoryTrackerInterface) service).getService());
+                context.unbindService(this);
+                if (andThen != null) andThen.run();
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+            }
+        };
+        Log.v(TAG, "attempting to bind to memory tracker");
+        context.bindService(new Intent(context, MemoryTracker.class),
+                connection, Context.BIND_AUTO_CREATE);
+    }
+}
diff --git a/src/com/android/launcher3/MemoryTracker.java b/src/com/android/launcher3/MemoryTracker.java
new file mode 100644
index 0000000..2d37c80
--- /dev/null
+++ b/src/com/android/launcher3/MemoryTracker.java
@@ -0,0 +1,219 @@
+/*
+ * 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.launcher3;
+
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.*;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MemoryTracker extends Service {
+    public static final String TAG = MemoryTracker.class.getSimpleName();
+    public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING";
+
+    private static final long UPDATE_RATE = 5000;
+
+    private static final int MSG_START = 1;
+    private static final int MSG_STOP = 2;
+    private static final int MSG_UPDATE = 3;
+
+    public static class ProcessMemInfo {
+        public int pid;
+        public String name;
+        public long startTime;
+        public long currentPss, currentUss;
+        public long[] pss = new long[256];
+        public long[] uss = new long[256];
+            //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes
+        public long max = 1;
+        public int head = 0;
+        public ProcessMemInfo(int pid, String name, long start) {
+            this.pid = pid;
+            this.name = name;
+            this.startTime = start;
+        }
+        public long getUptime() {
+            return System.currentTimeMillis() - startTime;
+        }
+    };
+    public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
+    public final ArrayList<Long> mPids = new ArrayList<Long>();
+    private int[] mPidsArray = new int[0];
+    private final Object mLock = new Object();
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_START:
+                    mHandler.removeMessages(MSG_UPDATE);
+                    mHandler.sendEmptyMessage(MSG_UPDATE);
+                    break;
+                case MSG_STOP:
+                    mHandler.removeMessages(MSG_UPDATE);
+                    break;
+                case MSG_UPDATE:
+                    update();
+                    mHandler.removeMessages(MSG_UPDATE);
+                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
+                    break;
+            }
+        }
+    };
+
+    ActivityManager mAm;
+
+    public static void startTrackingMe(Context context, String name) {
+        context.startService(new Intent(context, MemoryTracker.class)
+                .setAction(MemoryTracker.ACTION_START_TRACKING)
+                .putExtra("pid", android.os.Process.myPid())
+                .putExtra("name", name)
+        );
+    }
+
+    public ProcessMemInfo getMemInfo(int pid) {
+        return mData.get(pid);
+    }
+
+    public int[] getTrackedProcesses() {
+        return mPidsArray;
+    }
+
+    public void startTrackingProcess(int pid, String name, long start) {
+        synchronized (mLock) {
+            final Long lpid = new Long(pid);
+
+            if (mPids.contains(lpid)) return;
+
+            mPids.add(lpid);
+            updatePidsArrayL();
+
+            mData.put(pid, new ProcessMemInfo(pid, name, start));
+        }
+    }
+
+    void updatePidsArrayL() {
+        final int N = mPids.size();
+        mPidsArray = new int[N];
+        StringBuffer sb = new StringBuffer("Now tracking processes: ");
+        for (int i=0; i<N; i++) {
+            final int p = mPids.get(i).intValue();
+            mPidsArray[i] = p;
+            sb.append(p); sb.append(" ");
+        }
+        Log.v(TAG, sb.toString());
+    }
+
+    void update() {
+        synchronized (mLock) {
+            Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
+            for (int i=0; i<dinfos.length; i++) {
+                Debug.MemoryInfo dinfo = dinfos[i];
+                if (i > mPids.size()) {
+                    Log.e(TAG, "update: unknown process info received: " + dinfo);
+                    break;
+                }
+                final long pid = mPids.get(i).intValue();
+                final ProcessMemInfo info = mData.get(pid);
+                info.head = (info.head+1) % info.pss.length;
+                info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
+                info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
+                if (info.currentPss > info.max) info.max = info.currentPss;
+                if (info.currentUss > info.max) info.max = info.currentUss;
+                // Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
+                if (info.currentPss == 0) {
+                    Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
+                    mData.remove(pid);
+                }
+            }
+            for (int i=mPids.size()-1; i>=0; i--) {
+                final long pid = mPids.get(i).intValue();
+                if (mData.get(pid) == null) {
+                    mPids.remove(i);
+                    updatePidsArrayL();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+
+        // catch up in case we crashed but other processes are still running
+        List<ActivityManager.RunningServiceInfo> svcs = mAm.getRunningServices(256);
+        for (ActivityManager.RunningServiceInfo svc : svcs) {
+            if (svc.service.getPackageName().equals(getPackageName())) {
+                Log.v(TAG, "discovered running service: " + svc.process + " (" + svc.pid + ")");
+                startTrackingProcess(svc.pid, svc.process,
+                        System.currentTimeMillis() - (SystemClock.elapsedRealtime() - svc.activeSince));
+            }
+        }
+
+        List<ActivityManager.RunningAppProcessInfo> procs = mAm.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo proc : procs) {
+            final String pname = proc.processName;
+            if (pname.startsWith(getPackageName())) {
+                Log.v(TAG, "discovered other running process: " + pname + " (" + proc.pid + ")");
+                startTrackingProcess(proc.pid, pname, System.currentTimeMillis());
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandler.sendEmptyMessage(MSG_STOP);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.v(TAG, "Received start id " + startId + ": " + intent);
+
+        if (intent != null) {
+            if (ACTION_START_TRACKING.equals(intent.getAction())) {
+                final int pid = intent.getIntExtra("pid", -1);
+                final String name = intent.getStringExtra("name");
+                final long start = intent.getLongExtra("start", System.currentTimeMillis());
+                startTrackingProcess(pid, name, start);
+            }
+        }
+
+        mHandler.sendEmptyMessage(MSG_START);
+
+        return START_STICKY;
+    }
+
+    public class MemoryTrackerInterface extends Binder {
+        MemoryTracker getService() {
+            return MemoryTracker.this;
+        }
+    }
+
+    private final IBinder mBinder = new MemoryTrackerInterface();
+
+    public IBinder onBind(Intent intent) {
+        mHandler.sendEmptyMessage(MSG_START);
+
+        return mBinder;
+    }
+}
diff --git a/src/com/android/launcher3/PackageChangedReceiver.java b/src/com/android/launcher3/PackageChangedReceiver.java
new file mode 100644
index 0000000..e59f6d8
--- /dev/null
+++ b/src/com/android/launcher3/PackageChangedReceiver.java
@@ -0,0 +1,21 @@
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class PackageChangedReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        final String packageName = intent.getData().getSchemeSpecificPart();
+
+        if (packageName == null || packageName.length() == 0) {
+            // they sent us a bad intent
+            return;
+        }
+        // in rare cases the receiver races with the application to set up LauncherAppState
+        LauncherAppState.setApplicationContext(context.getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+        WidgetPreviewLoader.removePackageFromDb(app.getWidgetPreviewCacheDb(), packageName);
+    }
+}
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java
new file mode 100644
index 0000000..08e5f72
--- /dev/null
+++ b/src/com/android/launcher3/PageIndicator.java
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.animation.TimeInterpolator;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+
+public class PageIndicator extends LinearLayout {
+    @SuppressWarnings("unused")
+    private static final String TAG = "PageIndicator";
+    // Want this to look good? Keep it odd
+    private static final boolean MODULATE_ALPHA_ENABLED = false;
+
+    private LayoutInflater mLayoutInflater;
+    private int[] mWindowRange = new int[2];
+    private int mMaxWindowSize;
+
+    private ArrayList<PageIndicatorMarker> mMarkers =
+            new ArrayList<PageIndicatorMarker>();
+    private int mActiveMarkerIndex;
+
+    public static class PageMarkerResources {
+        int activeId;
+        int inactiveId;
+
+        public PageMarkerResources() {
+            activeId = R.drawable.ic_pageindicator_current;
+            inactiveId = R.drawable.ic_pageindicator_default;
+        }
+        public PageMarkerResources(int aId, int iaId) {
+            activeId = aId;
+            inactiveId = iaId;
+        }
+    }
+
+    public PageIndicator(Context context) {
+        this(context, null);
+    }
+
+    public PageIndicator(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.PageIndicator, defStyle, 0);
+        mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15);
+        mWindowRange[0] = 0;
+        mWindowRange[1] = 0;
+        mLayoutInflater = LayoutInflater.from(context);
+        a.recycle();
+
+        // Set the layout transition properties
+        LayoutTransition transition = getLayoutTransition();
+        transition.setDuration(175);
+    }
+
+    private void enableLayoutTransitions() {
+        LayoutTransition transition = getLayoutTransition();
+        transition.enableTransitionType(LayoutTransition.APPEARING);
+        transition.enableTransitionType(LayoutTransition.DISAPPEARING);
+        transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+    }
+
+    private void disableLayoutTransitions() {
+        LayoutTransition transition = getLayoutTransition();
+        transition.disableTransitionType(LayoutTransition.APPEARING);
+        transition.disableTransitionType(LayoutTransition.DISAPPEARING);
+        transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+    }
+
+    void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
+        if (activeIndex < 0) {
+            new Throwable().printStackTrace();
+        }
+        int windowSize = Math.min(mMarkers.size(), mMaxWindowSize);
+        int hWindowSize = (int) windowSize / 2;
+        float hfWindowSize = windowSize / 2f;
+        int windowStart = Math.max(0, activeIndex - hWindowSize);
+        int windowEnd = Math.min(mMarkers.size(), windowStart + mMaxWindowSize);
+        windowStart = windowEnd - Math.min(mMarkers.size(), windowSize);
+        int windowMid = windowStart + (windowEnd - windowStart) / 2;
+        boolean windowAtStart = (windowStart == 0);
+        boolean windowAtEnd = (windowEnd == mMarkers.size());
+        boolean windowMoved = (mWindowRange[0] != windowStart) ||
+                (mWindowRange[1] != windowEnd);
+
+        if (!allowAnimations) {
+            disableLayoutTransitions();
+        }
+
+        // Remove all the previous children that are no longer in the window
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
+            int markerIndex = mMarkers.indexOf(marker);
+            if (markerIndex < windowStart || markerIndex >= windowEnd) {
+                removeView(marker);
+            }
+        }
+
+        // Add all the new children that belong in the window
+        for (int i = 0; i < mMarkers.size(); ++i) {
+            PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i);
+            if (windowStart <= i && i < windowEnd) {
+                if (indexOfChild(marker) < 0) {
+                    addView(marker, i - windowStart);
+                }
+                if (i == activeIndex) {
+                    marker.activate(windowMoved);
+                } else {
+                    marker.inactivate(windowMoved);
+                }
+            } else {
+                marker.inactivate(true);
+            }
+
+            if (MODULATE_ALPHA_ENABLED) {
+                // Update the marker's alpha
+                float alpha = 1f;
+                if (mMarkers.size() > windowSize) {
+                    if ((windowAtStart && i > hWindowSize) ||
+                        (windowAtEnd && i < (mMarkers.size() - hWindowSize)) ||
+                        (!windowAtStart && !windowAtEnd)) {
+                        alpha = 1f - Math.abs((i - windowMid) / hfWindowSize);
+                    }
+                }
+                marker.animate().alpha(alpha).setDuration(500).start();
+            }
+        }
+
+        if (!allowAnimations) {
+            enableLayoutTransitions();
+        }
+
+        mWindowRange[0] = windowStart;
+        mWindowRange[1] = windowEnd;
+    }
+
+    void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) {
+        index = Math.max(0, Math.min(index, mMarkers.size()));
+
+        PageIndicatorMarker m =
+            (PageIndicatorMarker) mLayoutInflater.inflate(R.layout.page_indicator_marker,
+                    this, false);
+        m.setMarkerDrawables(marker.activeId, marker.inactiveId);
+
+        mMarkers.add(index, m);
+        offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
+    }
+    void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
+        for (int i = 0; i < markers.size(); ++i) {
+            addMarker(Integer.MAX_VALUE, markers.get(i), allowAnimations);
+        }
+    }
+
+    void updateMarker(int index, PageMarkerResources marker) {
+        PageIndicatorMarker m = mMarkers.get(index);
+        m.setMarkerDrawables(marker.activeId, marker.inactiveId);
+    }
+
+    void removeMarker(int index, boolean allowAnimations) {
+        if (mMarkers.size() > 0) {
+            index = Math.max(0, Math.min(mMarkers.size() - 1, index));
+            mMarkers.remove(index);
+            offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
+        }
+    }
+    void removeAllMarkers(boolean allowAnimations) {
+        while (mMarkers.size() > 0) {
+            removeMarker(Integer.MAX_VALUE, allowAnimations);
+        }
+    }
+
+    void setActiveMarker(int index) {
+        // Center the active marker
+        mActiveMarkerIndex = index;
+        offsetWindowCenterTo(index, false);
+    }
+
+    void dumpState(String txt) {
+        System.out.println(txt);
+        System.out.println("\tmMarkers: " + mMarkers.size());
+        for (int i = 0; i < mMarkers.size(); ++i) {
+            PageIndicatorMarker m = mMarkers.get(i);
+            System.out.println("\t\t(" + i + ") " + m);
+        }
+        System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]");
+        System.out.println("\tchildren: " + getChildCount());
+        for (int i = 0; i < getChildCount(); ++i) {
+            PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i);
+            System.out.println("\t\t(" + i + ") " + m);
+        }
+        System.out.println("\tactive: " + mActiveMarkerIndex);
+    }
+}
diff --git a/src/com/android/launcher3/PageIndicatorMarker.java b/src/com/android/launcher3/PageIndicatorMarker.java
new file mode 100644
index 0000000..b1025d6
--- /dev/null
+++ b/src/com/android/launcher3/PageIndicatorMarker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.FrameLayout;
+import com.android.launcher3.R;
+
+public class PageIndicatorMarker extends FrameLayout {
+    @SuppressWarnings("unused")
+    private static final String TAG = "PageIndicator";
+
+    private static final int MARKER_FADE_DURATION = 175;
+
+    private ImageView mActiveMarker;
+    private ImageView mInactiveMarker;
+    private boolean mIsActive = false;
+
+    public PageIndicatorMarker(Context context) {
+        this(context, null);
+    }
+
+    public PageIndicatorMarker(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PageIndicatorMarker(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    protected void onFinishInflate() {
+        mActiveMarker = (ImageView) findViewById(R.id.active);
+        mInactiveMarker = (ImageView) findViewById(R.id.inactive);
+    }
+
+    void setMarkerDrawables(int activeResId, int inactiveResId) {
+        Resources r = getResources();
+        mActiveMarker.setImageDrawable(r.getDrawable(activeResId));
+        mInactiveMarker.setImageDrawable(r.getDrawable(inactiveResId));
+    }
+
+    void activate(boolean immediate) {
+        if (immediate) {
+            mActiveMarker.animate().cancel();
+            mActiveMarker.setAlpha(1f);
+            mActiveMarker.setScaleX(1f);
+            mActiveMarker.setScaleY(1f);
+            mInactiveMarker.animate().cancel();
+            mInactiveMarker.setAlpha(0f);
+        } else {
+            mActiveMarker.animate()
+                    .alpha(1f)
+                    .scaleX(1f)
+                    .scaleY(1f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+            mInactiveMarker.animate()
+                    .alpha(0f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+        }
+        mIsActive = true;
+    }
+
+    void inactivate(boolean immediate) {
+        if (immediate) {
+            mInactiveMarker.animate().cancel();
+            mInactiveMarker.setAlpha(1f);
+            mActiveMarker.animate().cancel();
+            mActiveMarker.setAlpha(0f);
+            mActiveMarker.setScaleX(0.5f);
+            mActiveMarker.setScaleY(0.5f);
+        } else {
+            mInactiveMarker.animate().alpha(1f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+            mActiveMarker.animate()
+                    .alpha(0f)
+                    .scaleX(0.5f)
+                    .scaleY(0.5f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+        }
+        mIsActive = false;
+    }
+
+    boolean isActive() {
+        return mIsActive;
+    }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
new file mode 100644
index 0000000..e982985
--- /dev/null
+++ b/src/com/android/launcher3/PagedView.java
@@ -0,0 +1,2818 @@
+/*
+ * 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+
+interface Page {
+    public int getPageChildCount();
+    public View getChildOnPageAt(int i);
+    public void removeAllViewsOnPage();
+    public void removeViewOnPageAt(int i);
+    public int indexOfChildOnPage(View v);
+}
+
+/**
+ * An abstraction of the original Workspace which supports browsing through a
+ * sequential list of "pages"
+ */
+public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+    private static final String TAG = "PagedView";
+    private static final boolean DEBUG = false;
+    protected static final int INVALID_PAGE = -1;
+
+    // the min drag distance for a fling to register, to prevent random page shifts
+    private static final int MIN_LENGTH_FOR_FLING = 25;
+
+    protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
+    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
+    protected static final float NANOTIME_DIV = 1000000000.0f;
+
+    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
+    private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
+
+    private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
+    // The page is moved more than halfway, automatically move to the next page on touch up.
+    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
+
+    // The following constants need to be scaled based on density. The scaled versions will be
+    // assigned to the corresponding member variables below.
+    private static final int FLING_THRESHOLD_VELOCITY = 500;
+    private static final int MIN_SNAP_VELOCITY = 1500;
+    private static final int MIN_FLING_VELOCITY = 250;
+
+    // We are disabling touch interaction of the widget region for factory ROM.
+    private static final boolean DISABLE_TOUCH_INTERACTION = false;
+    private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
+    private static final boolean DISABLE_FLING_TO_DELETE = true;
+
+    public static final int INVALID_RESTORE_PAGE = -1001;
+
+    private boolean mFreeScroll = false;
+    private int mFreeScrollMinScrollX = -1;
+    private int mFreeScrollMaxScrollX = -1;
+
+    static final int AUTOMATIC_PAGE_SPACING = -1;
+
+    protected int mFlingThresholdVelocity;
+    protected int mMinFlingVelocity;
+    protected int mMinSnapVelocity;
+
+    protected float mDensity;
+    protected float mSmoothingTime;
+    protected float mTouchX;
+
+    protected boolean mFirstLayout = true;
+    private int mNormalChildHeight;
+
+    protected int mCurrentPage;
+    protected int mRestorePage = INVALID_RESTORE_PAGE;
+    protected int mChildCountOnLastLayout;
+
+    protected int mNextPage = INVALID_PAGE;
+    protected int mMaxScrollX;
+    protected Scroller mScroller;
+    private VelocityTracker mVelocityTracker;
+
+    private float mParentDownMotionX;
+    private float mParentDownMotionY;
+    private float mDownMotionX;
+    private float mDownMotionY;
+    private float mDownScrollX;
+    private float mDragViewBaselineLeft;
+    protected float mLastMotionX;
+    protected float mLastMotionXRemainder;
+    protected float mLastMotionY;
+    protected float mTotalMotionX;
+    private int mLastScreenCenter = -1;
+
+    private boolean mCancelTap;
+
+    private int[] mPageScrolls;
+
+    protected final static int TOUCH_STATE_REST = 0;
+    protected final static int TOUCH_STATE_SCROLLING = 1;
+    protected final static int TOUCH_STATE_PREV_PAGE = 2;
+    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+    protected final static int TOUCH_STATE_REORDERING = 4;
+
+    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
+
+    protected int mTouchState = TOUCH_STATE_REST;
+    protected boolean mForceScreenScrolled = false;
+
+    protected OnLongClickListener mLongClickListener;
+
+    protected int mTouchSlop;
+    private int mPagingTouchSlop;
+    private int mMaximumVelocity;
+    protected int mPageSpacing;
+    protected int mPageLayoutPaddingTop;
+    protected int mPageLayoutPaddingBottom;
+    protected int mPageLayoutPaddingLeft;
+    protected int mPageLayoutPaddingRight;
+    protected int mPageLayoutWidthGap;
+    protected int mPageLayoutHeightGap;
+    protected int mCellCountX = 0;
+    protected int mCellCountY = 0;
+    protected boolean mCenterPagesVertically;
+    protected boolean mAllowOverScroll = true;
+    protected int mUnboundedScrollX;
+    protected int[] mTempVisiblePagesRange = new int[2];
+    protected boolean mForceDrawAllChildrenNextFrame;
+
+    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
+    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
+    // the screens from continuing to translate beyond the normal bounds.
+    protected int mOverScrollX;
+
+    protected static final int INVALID_POINTER = -1;
+
+    protected int mActivePointerId = INVALID_POINTER;
+
+    private PageSwitchListener mPageSwitchListener;
+
+    protected ArrayList<Boolean> mDirtyPageContent;
+
+    // If true, syncPages and syncPageItems will be called to refresh pages
+    protected boolean mContentIsRefreshable = true;
+
+    // If true, modify alpha of neighboring pages as user scrolls left/right
+    protected boolean mFadeInAdjacentScreens = false;
+
+    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
+    // to switch to a new page
+    protected boolean mUsePagingTouchSlop = true;
+
+    // If true, the subclass should directly update scrollX itself in its computeScroll method
+    // (SmoothPagedView does this)
+    protected boolean mDeferScrollUpdate = false;
+    protected boolean mDeferLoadAssociatedPagesUntilScrollCompletes = false;
+
+    protected boolean mIsPageMoving = false;
+
+    // All syncs and layout passes are deferred until data is ready.
+    protected boolean mIsDataReady = false;
+
+    protected boolean mAllowLongPress = true;
+
+    // Page Indicator
+    private int mPageIndicatorViewId;
+    private PageIndicator mPageIndicator;
+    private boolean mAllowPagedViewAnimations = true;
+
+    // The viewport whether the pages are to be contained (the actual view may be larger than the
+    // viewport)
+    private Rect mViewport = new Rect();
+
+    // Reordering
+    // We use the min scale to determine how much to expand the actually PagedView measured
+    // dimensions such that when we are zoomed out, the view is not clipped
+    private int REORDERING_DROP_REPOSITION_DURATION = 200;
+    protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
+    protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
+    private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
+    private float mMinScale = 1f;
+    private boolean mUseMinScale = false;
+    protected View mDragView;
+    protected AnimatorSet mZoomInOutAnim;
+    private Runnable mSidePageHoverRunnable;
+    private int mSidePageHoverIndex = -1;
+    // This variable's scope is only for the duration of startReordering() and endReordering()
+    private boolean mReorderingStarted = false;
+    // This variable's scope is for the duration of startReordering() and after the zoomIn()
+    // animation after endReordering()
+    private boolean mIsReordering;
+    // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
+    private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
+    private int mPostReorderingPreZoomInRemainingAnimationCount;
+    private Runnable mPostReorderingPreZoomInRunnable;
+
+    // Convenience/caching
+    private Matrix mTmpInvMatrix = new Matrix();
+    private float[] mTmpPoint = new float[2];
+    private int[] mTmpIntPoint = new int[2];
+    private Rect mTmpRect = new Rect();
+    private Rect mAltTmpRect = new Rect();
+
+    // Fling to delete
+    private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
+    private float FLING_TO_DELETE_FRICTION = 0.035f;
+    // The degrees specifies how much deviation from the up vector to still consider a fling "up"
+    private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
+    protected int mFlingToDeleteThresholdVelocity = -1400;
+    // Drag to delete
+    private boolean mDeferringForDelete = false;
+    private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
+    private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
+
+    // Drop to delete
+    private View mDeleteDropTarget;
+
+    private boolean mAutoComputePageSpacing = false;
+    private boolean mRecomputePageSpacing = false;
+
+    // Bouncer
+    private boolean mTopAlignPageWhenShrinkingForBouncer = false;
+
+    protected final Rect mInsets = new Rect();
+
+    protected int mFirstChildLeft;
+
+    public interface PageSwitchListener {
+        void onPageSwitch(View newPage, int newPageIndex);
+    }
+
+    public PagedView(Context context) {
+        this(context, null);
+    }
+
+    public PagedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.PagedView, defStyle, 0);
+        setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
+        if (mPageSpacing < 0) {
+            mAutoComputePageSpacing = mRecomputePageSpacing = true;
+        }
+        mPageLayoutPaddingTop = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutPaddingTop, 0);
+        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutPaddingBottom, 0);
+        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutPaddingLeft, 0);
+        mPageLayoutPaddingRight = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutPaddingRight, 0);
+        mPageLayoutWidthGap = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutWidthGap, 0);
+        mPageLayoutHeightGap = a.getDimensionPixelSize(
+                R.styleable.PagedView_pageLayoutHeightGap, 0);
+        mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
+        a.recycle();
+
+        setHapticFeedbackEnabled(false);
+        init();
+    }
+
+    /**
+     * Initializes various states for this workspace.
+     */
+    protected void init() {
+        mDirtyPageContent = new ArrayList<Boolean>();
+        mDirtyPageContent.ensureCapacity(32);
+        mScroller = new Scroller(getContext(), new ScrollInterpolator());
+        mCurrentPage = 0;
+        mCenterPagesVertically = true;
+
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = configuration.getScaledPagingTouchSlop();
+        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mDensity = getResources().getDisplayMetrics().density;
+
+        // Scale the fling-to-delete threshold by the density
+        mFlingToDeleteThresholdVelocity =
+                (int) (mFlingToDeleteThresholdVelocity * mDensity);
+
+        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
+        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
+        setOnHierarchyChangeListener(this);
+    }
+
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Hook up the page indicator
+        ViewGroup parent = (ViewGroup) getParent();
+        if (mPageIndicator == null && mPageIndicatorViewId > -1) {
+            mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
+            mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+
+            ArrayList<PageIndicator.PageMarkerResources> markers =
+                    new ArrayList<PageIndicator.PageMarkerResources>();
+            for (int i = 0; i < getChildCount(); ++i) {
+                markers.add(getPageIndicatorMarker(i));
+            }
+
+            mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations);
+
+            OnClickListener listener = getPageIndicatorClickListener();
+            if (listener != null) {
+                mPageIndicator.setOnClickListener(listener);
+            }
+            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+        }
+    }
+
+    protected String getPageIndicatorDescription() {
+        return getCurrentPageDescription();
+    }
+
+    protected OnClickListener getPageIndicatorClickListener() {
+        return null;
+    }
+
+    protected void onDetachedFromWindow() {
+        // Unhook the page indicator
+        mPageIndicator = null;
+    }
+
+    void setDeleteDropTarget(View v) {
+        mDeleteDropTarget = v;
+    }
+
+    // Convenience methods to map points from self to parent and vice versa
+    float[] mapPointFromViewToParent(View v, float x, float y) {
+        mTmpPoint[0] = x;
+        mTmpPoint[1] = y;
+        v.getMatrix().mapPoints(mTmpPoint);
+        mTmpPoint[0] += v.getLeft();
+        mTmpPoint[1] += v.getTop();
+        return mTmpPoint;
+    }
+    float[] mapPointFromParentToView(View v, float x, float y) {
+        mTmpPoint[0] = x - v.getLeft();
+        mTmpPoint[1] = y - v.getTop();
+        v.getMatrix().invert(mTmpInvMatrix);
+        mTmpInvMatrix.mapPoints(mTmpPoint);
+        return mTmpPoint;
+    }
+
+    void updateDragViewTranslationDuringDrag() {
+        if (mDragView != null) {
+            float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
+                    (mDragViewBaselineLeft - mDragView.getLeft());
+            float y = mLastMotionY - mDownMotionY;
+            mDragView.setTranslationX(x);
+            mDragView.setTranslationY(y);
+
+            if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
+                    + x + ", " + y);
+        }
+    }
+
+    public void setMinScale(float f) {
+        mMinScale = f;
+        mUseMinScale = true;
+        requestLayout();
+    }
+
+    @Override
+    public void setScaleX(float scaleX) {
+        super.setScaleX(scaleX);
+        if (isReordering(true)) {
+            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+            mLastMotionX = p[0];
+            mLastMotionY = p[1];
+            updateDragViewTranslationDuringDrag();
+        }
+    }
+
+    // Convenience methods to get the actual width/height of the PagedView (since it is measured
+    // to be larger to account for the minimum possible scale)
+    int getViewportWidth() {
+        return mViewport.width();
+    }
+    int getViewportHeight() {
+        return mViewport.height();
+    }
+
+    // Convenience methods to get the offset ASSUMING that we are centering the pages in the
+    // PagedView both horizontally and vertically
+    int getViewportOffsetX() {
+        return (getMeasuredWidth() - getViewportWidth()) / 2;
+    }
+
+    int getViewportOffsetY() {
+        return (getMeasuredHeight() - getViewportHeight()) / 2;
+    }
+
+    PageIndicator getPageIndicator() {
+        return mPageIndicator;
+    }
+    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+        return new PageIndicator.PageMarkerResources();
+    }
+
+    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
+        mPageSwitchListener = pageSwitchListener;
+        if (mPageSwitchListener != null) {
+            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+        }
+    }
+
+    /**
+     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+     */
+    public boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    /**
+     * Called by subclasses to mark that data is ready, and that we can begin loading and laying
+     * out pages.
+     */
+    protected void setDataIsReady() {
+        mIsDataReady = true;
+    }
+
+    protected boolean isDataReady() {
+        return mIsDataReady;
+    }
+
+    /**
+     * Returns the index of the currently displayed page.
+     *
+     * @return The index of the currently displayed page.
+     */
+    int getCurrentPage() {
+        return mCurrentPage;
+    }
+
+    int getNextPage() {
+        return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+    }
+
+    int getPageCount() {
+        return getChildCount();
+    }
+
+    View getPageAt(int index) {
+        return getChildAt(index);
+    }
+
+    protected int indexToPage(int index) {
+        return index;
+    }
+
+    /**
+     * Updates the scroll of the current page immediately to its final scroll position.  We use this
+     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
+     * the previous tab page.
+     */
+    protected void updateCurrentPageScroll() {
+        // If the current page is invalid, just reset the scroll position to zero
+        int newX = 0;
+        if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
+            newX = getScrollForPage(mCurrentPage);
+        }
+        scrollTo(newX, 0);
+        mScroller.setFinalX(newX);
+        mScroller.forceFinished(true);
+    }
+
+    /**
+     * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
+     * ends, {@link #resumeScrolling()} should be called, along with
+     * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
+     */
+    void pauseScrolling() {
+        mScroller.forceFinished(true);
+    }
+
+    /**
+     * Enables scrolling again.
+     * @see #pauseScrolling()
+     */
+    void resumeScrolling() {
+    }
+    /**
+     * Sets the current page.
+     */
+    void setCurrentPage(int currentPage) {
+        if (!mScroller.isFinished()) {
+            mScroller.abortAnimation();
+            // We need to clean up the next page here to avoid computeScrollHelper from
+            // updating current page on the pass.
+            mNextPage = INVALID_PAGE;
+        }
+        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
+        // the default
+        if (getChildCount() == 0) {
+            return;
+        }
+        mForceScreenScrolled = true;
+        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
+        updateCurrentPageScroll();
+        notifyPageSwitchListener();
+        invalidate();
+    }
+
+    /**
+     * The restore page will be set in place of the current page at the next (likely first)
+     * layout.
+     */
+    void setRestorePage(int restorePage) {
+        mRestorePage = restorePage;
+    }
+
+    protected void notifyPageSwitchListener() {
+        if (mPageSwitchListener != null) {
+            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+        }
+
+        // Update the page indicator (when we aren't reordering)
+        if (mPageIndicator != null && !isReordering(false)) {
+            mPageIndicator.setActiveMarker(getNextPage());
+        }
+    }
+    protected void pageBeginMoving() {
+        if (!mIsPageMoving) {
+            mIsPageMoving = true;
+            onPageBeginMoving();
+        }
+    }
+
+    protected void pageEndMoving() {
+        if (mIsPageMoving) {
+            mIsPageMoving = false;
+            onPageEndMoving();
+        }
+    }
+
+    protected boolean isPageMoving() {
+        return mIsPageMoving;
+    }
+
+    // a method that subclasses can override to add behavior
+    protected void onPageBeginMoving() {
+    }
+
+    // a method that subclasses can override to add behavior
+    protected void onPageEndMoving() {
+    }
+
+    /**
+     * Registers the specified listener on each page contained in this workspace.
+     *
+     * @param l The listener used to respond to long clicks.
+     */
+    @Override
+    public void setOnLongClickListener(OnLongClickListener l) {
+        mLongClickListener = l;
+        final int count = getPageCount();
+        for (int i = 0; i < count; i++) {
+            getPageAt(i).setOnLongClickListener(l);
+        }
+        super.setOnLongClickListener(l);
+    }
+
+    @Override
+    public void scrollBy(int x, int y) {
+        scrollTo(mUnboundedScrollX + x, getScrollY() + y);
+    }
+
+    @Override
+    public void scrollTo(int x, int y) {
+        // In free scroll mode, we clamp the scrollX
+        if (mFreeScroll) {
+            x = Math.min(x, mFreeScrollMaxScrollX);
+            x = Math.max(x, mFreeScrollMinScrollX);
+        }
+
+        final boolean isRtl = isLayoutRtl();
+        mUnboundedScrollX = x;
+
+        boolean isXBeforeFirstPage = isRtl ? (x > mMaxScrollX) : (x < 0);
+        boolean isXAfterLastPage = isRtl ? (x < 0) : (x > mMaxScrollX);
+        if (isXBeforeFirstPage) {
+            super.scrollTo(0, y);
+            if (mAllowOverScroll) {
+                if (isRtl) {
+                    overScroll(x - mMaxScrollX);
+                } else {
+                    overScroll(x);
+                }
+            }
+        } else if (isXAfterLastPage) {
+            super.scrollTo(mMaxScrollX, y);
+            if (mAllowOverScroll) {
+                if (isRtl) {
+                    overScroll(x);
+                } else {
+                    overScroll(x - mMaxScrollX);
+                }
+            }
+        } else {
+            mOverScrollX = x;
+            super.scrollTo(x, y);
+        }
+
+        mTouchX = x;
+        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+
+        // Update the last motion events when scrolling
+        if (isReordering(true)) {
+            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+            mLastMotionX = p[0];
+            mLastMotionY = p[1];
+            updateDragViewTranslationDuringDrag();
+        }
+    }
+
+    private void sendScrollAccessibilityEvent() {
+        AccessibilityManager am =
+                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (am.isEnabled()) {
+            AccessibilityEvent ev =
+                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            ev.setItemCount(getChildCount());
+            ev.setFromIndex(mCurrentPage);
+
+            final int action;
+            if (getNextPage() >= mCurrentPage) {
+                action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+            } else {
+                action = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+            }
+
+            ev.setAction(action);
+            sendAccessibilityEventUnchecked(ev);
+        }
+    }
+
+    // we moved this functionality to a helper function so SmoothPagedView can reuse it
+    protected boolean computeScrollHelper() {
+        if (mScroller.computeScrollOffset()) {
+            // Don't bother scrolling if the page does not need to be moved
+            if (getScrollX() != mScroller.getCurrX()
+                || getScrollY() != mScroller.getCurrY()
+                || mOverScrollX != mScroller.getCurrX()) {
+                float scaleX = mFreeScroll ? getScaleX() : 1f;
+                int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
+                scrollTo(scrollX, mScroller.getCurrY());
+            }
+            invalidate();
+            return true;
+        } else if (mNextPage != INVALID_PAGE) {
+            sendScrollAccessibilityEvent();
+
+            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
+            mNextPage = INVALID_PAGE;
+            notifyPageSwitchListener();
+
+            // Load the associated pages if necessary
+            if (mDeferLoadAssociatedPagesUntilScrollCompletes) {
+                loadAssociatedPages(mCurrentPage);
+                mDeferLoadAssociatedPagesUntilScrollCompletes = false;
+            }
+
+            // We don't want to trigger a page end moving unless the page has settled
+            // and the user has stopped scrolling
+            if (mTouchState == TOUCH_STATE_REST) {
+                pageEndMoving();
+            }
+
+            onPostReorderingAnimationCompleted();
+            AccessibilityManager am = (AccessibilityManager)
+                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            if (am.isEnabled()) {
+                // Notify the user when the page changes
+                announceForAccessibility(getCurrentPageDescription());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void computeScroll() {
+        computeScrollHelper();
+    }
+
+    protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
+        return mTopAlignPageWhenShrinkingForBouncer;
+    }
+
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        public boolean isFullScreenPage = false;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+    }
+
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    public void addFullScreenPage(View page) {
+        LayoutParams lp = generateDefaultLayoutParams();
+        lp.isFullScreenPage = true;
+        super.addView(page, 0, lp);
+    }
+
+    public int getNormalChildHeight() {
+        return mNormalChildHeight;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!mIsDataReady || getChildCount() == 0) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+
+        // We measure the dimensions of the PagedView to be larger than the pages so that when we
+        // zoom out (and scale down), the view is still contained in the parent
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
+        // viewport, we can be at most one and a half screens offset once we scale down
+        DisplayMetrics dm = getResources().getDisplayMetrics();
+        int maxSize = Math.max(dm.widthPixels, dm.heightPixels + mInsets.top + mInsets.bottom);
+
+        int parentWidthSize, parentHeightSize;
+        int scaledWidthSize, scaledHeightSize;
+        if (mUseMinScale) {
+            parentWidthSize = (int) (1.5f * maxSize);
+            parentHeightSize = maxSize;
+            scaledWidthSize = (int) (parentWidthSize / mMinScale);
+            scaledHeightSize = (int) (parentHeightSize / mMinScale);
+        } else {
+            scaledWidthSize = widthSize;
+            scaledHeightSize = heightSize;
+        }
+        mViewport.set(0, 0, widthSize, heightSize);
+
+        if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+
+        // Return early if we aren't given a proper dimension
+        if (widthSize <= 0 || heightSize <= 0) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+
+        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
+         * of the All apps view on XLarge displays to not take up more space then it needs. Width
+         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
+         * each page to have the same width.
+         */
+        final int verticalPadding = getPaddingTop() + getPaddingBottom();
+        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+
+        // The children are given the same width and height as the workspace
+        // unless they were set to WRAP_CONTENT
+        if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
+        if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
+        if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
+        if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
+        if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            // disallowing padding in paged view (just pass 0)
+            final View child = getPageAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                int childWidthMode;
+                int childHeightMode;
+                int childWidth;
+                int childHeight;
+
+                if (!lp.isFullScreenPage) {
+                    if (lp.width == LayoutParams.WRAP_CONTENT) {
+                        childWidthMode = MeasureSpec.AT_MOST;
+                    } else {
+                        childWidthMode = MeasureSpec.EXACTLY;
+                    }
+
+                    if (lp.height == LayoutParams.WRAP_CONTENT) {
+                        childHeightMode = MeasureSpec.AT_MOST;
+                    } else {
+                        childHeightMode = MeasureSpec.EXACTLY;
+                    }
+
+                    childWidth = widthSize - horizontalPadding;
+                    childHeight = heightSize - verticalPadding - mInsets.top - mInsets.bottom;
+                    mNormalChildHeight = childHeight;
+
+                } else {
+                    childWidthMode = MeasureSpec.EXACTLY;
+                    childHeightMode = MeasureSpec.EXACTLY;
+
+                    if (mUseMinScale) {
+                        childWidth = getViewportWidth();
+                        childHeight = getViewportHeight();
+                    } else {
+                        childWidth = widthSize - getPaddingLeft() - getPaddingRight();
+                        childHeight = heightSize - getPaddingTop() - getPaddingBottom();
+                    }
+                }
+
+                final int childWidthMeasureSpec =
+                        MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
+                    final int childHeightMeasureSpec =
+                        MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+            }
+        }
+        setMeasuredDimension(scaledWidthSize, scaledHeightSize);
+
+        if (childCount > 0) {
+            // Calculate the variable page spacing if necessary
+            if (mAutoComputePageSpacing && mRecomputePageSpacing) {
+                // The gap between pages in the PagedView should be equal to the gap from the page
+                // to the edge of the screen (so it is not visible in the current screen).  To
+                // account for unequal padding on each side of the paged view, we take the maximum
+                // of the left/right gap and use that as the gap between each page.
+                int offset = (getViewportWidth() - getChildWidth(0)) / 2;
+                int spacing = Math.max(offset, widthSize - offset -
+                        getChildAt(0).getMeasuredWidth());
+                setPageSpacing(spacing);
+                mRecomputePageSpacing = false;
+            }
+        }
+    }
+
+    public void setPageSpacing(int pageSpacing) {
+        mPageSpacing = pageSpacing;
+        requestLayout();
+    }
+
+    protected int getFirstChildLeft() {
+        return mFirstChildLeft;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (!mIsDataReady || getChildCount() == 0) {
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
+        final int childCount = getChildCount();
+
+        int screenWidth = getViewportWidth();
+
+        int offsetX = getViewportOffsetX();
+        int offsetY = getViewportOffsetY();
+
+        // Update the viewport offsets
+        mViewport.offset(offsetX,  offsetY);
+
+        final boolean isRtl = isLayoutRtl();
+
+        final int startIndex = isRtl ? childCount - 1 : 0;
+        final int endIndex = isRtl ? -1 : childCount;
+        final int delta = isRtl ? -1 : 1;
+
+        int verticalPadding = getPaddingTop() + getPaddingBottom();
+
+        int childLeft = mFirstChildLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
+        if (mPageScrolls == null || getChildCount() != mChildCountOnLastLayout) {
+            mPageScrolls = new int[getChildCount()];
+        }
+
+        for (int i = startIndex; i != endIndex; i += delta) {
+            final View child = getPageAt(i);
+            if (child.getVisibility() != View.GONE) {
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                int childTop;
+                if (lp.isFullScreenPage) {
+                    childTop = offsetY;
+                } else {
+                    childTop = offsetY + getPaddingTop() + mInsets.top;
+                    if (mCenterPagesVertically) {
+                        childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
+                    }
+                }
+
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
+                child.layout(childLeft, childTop,
+                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
+
+                // We assume the left and right padding are equal, and hence center the pages
+                // horizontally
+                int scrollOffset = (getViewportWidth() - childWidth) / 2;
+                mPageScrolls[i] = childLeft - scrollOffset - offsetX;
+
+                if (i != endIndex - delta) {
+                    childLeft += childWidth + scrollOffset;
+                    int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) / 2;
+                    childLeft += nextScrollOffset;
+                }
+            }
+        }
+
+        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+            setHorizontalScrollBarEnabled(false);
+            updateCurrentPageScroll();
+            setHorizontalScrollBarEnabled(true);
+            mFirstLayout = false;
+        }
+
+        if (childCount > 0) {
+            final int index = isLayoutRtl() ? 0 : childCount - 1;
+            mMaxScrollX = getScrollForPage(index);
+        } else {
+            mMaxScrollX = 0;
+        }
+
+        if (mScroller.isFinished() && mChildCountOnLastLayout != getChildCount() &&
+                !mDeferringForDelete) {
+            if (mRestorePage != INVALID_RESTORE_PAGE) {
+                setCurrentPage(mRestorePage);
+                mRestorePage = INVALID_RESTORE_PAGE;
+            } else {
+                setCurrentPage(getNextPage());
+            }
+        }
+        mChildCountOnLastLayout = getChildCount();
+
+        if (isReordering(true)) {
+            updateDragViewTranslationDuringDrag();
+        }
+    }
+
+    protected void screenScrolled(int screenCenter) {
+        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+
+        if (mFadeInAdjacentScreens && !isInOverscroll) {
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (child != null) {
+                    float scrollProgress = getScrollProgress(screenCenter, child, i);
+                    float alpha = 1 - Math.abs(scrollProgress);
+                    child.setAlpha(alpha);
+                }
+            }
+            invalidate();
+        }
+    }
+
+    protected void enablePagedViewAnimations() {
+        mAllowPagedViewAnimations = true;
+
+    }
+    protected void disablePagedViewAnimations() {
+        mAllowPagedViewAnimations = false;
+    }
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        // Update the page indicator, we don't update the page indicator as we
+        // add/remove pages
+        if (mPageIndicator != null && !isReordering(false)) {
+            int pageIndex = indexOfChild(child);
+            mPageIndicator.addMarker(pageIndex,
+                    getPageIndicatorMarker(pageIndex),
+                    mAllowPagedViewAnimations);
+        }
+
+        // This ensures that when children are added, they get the correct transforms / alphas
+        // in accordance with any scroll effects.
+        mForceScreenScrolled = true;
+        mRecomputePageSpacing = true;
+        updateFreescrollBounds();
+        invalidate();
+    }
+
+    @Override
+    public void onChildViewRemoved(View parent, View child) {
+        mForceScreenScrolled = true;
+        updateFreescrollBounds();
+        invalidate();
+    }
+
+    private void removeMarkerForView(int index) {
+        // Update the page indicator, we don't update the page indicator as we
+        // add/remove pages
+        if (mPageIndicator != null && !isReordering(false)) {
+            mPageIndicator.removeMarker(index, mAllowPagedViewAnimations);
+        }
+    }
+
+    @Override
+    public void removeView(View v) {
+        // XXX: We should find a better way to hook into this before the view
+        // gets removed form its parent...
+        removeMarkerForView(indexOfChild(v));
+        super.removeView(v);
+    }
+    @Override
+    public void removeViewInLayout(View v) {
+        // XXX: We should find a better way to hook into this before the view
+        // gets removed form its parent...
+        removeMarkerForView(indexOfChild(v));
+        super.removeViewInLayout(v);
+    }
+    @Override
+    public void removeViewAt(int index) {
+        // XXX: We should find a better way to hook into this before the view
+        // gets removed form its parent...
+        removeViewAt(index);
+        super.removeViewAt(index);
+    }
+    @Override
+    public void removeAllViewsInLayout() {
+        // Update the page indicator, we don't update the page indicator as we
+        // add/remove pages
+        if (mPageIndicator != null) {
+            mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+        }
+
+        super.removeAllViewsInLayout();
+    }
+
+    protected int getChildOffset(int index) {
+        if (index < 0 || index > getChildCount() - 1) return 0;
+
+        int offset = getPageAt(index).getLeft() - getViewportOffsetX();
+
+        return offset;
+    }
+
+    protected void getOverviewModePages(int[] range) {
+        range[0] = 0;
+        range[1] = Math.max(0, getChildCount() - 1);
+    }
+
+    protected void getVisiblePages(int[] range) {
+        final int pageCount = getChildCount();
+        mTmpIntPoint[0] = mTmpIntPoint[1] = 0;
+
+        range[0] = -1;
+        range[1] = -1;
+
+        if (pageCount > 0) {
+            int viewportWidth = getViewportWidth();
+            int curScreen = 0;
+
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                View currPage = getPageAt(i);
+
+                mTmpIntPoint[0] = 0;
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+                if (mTmpIntPoint[0] > viewportWidth) {
+                    if (range[0] == -1) {
+                        continue;
+                    } else {
+                        break;
+                    }
+                }
+
+                mTmpIntPoint[0] = currPage.getMeasuredWidth();
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+                if (mTmpIntPoint[0] < 0) {
+                    if (range[0] == -1) {
+                        continue;
+                    } else {
+                        break;
+                    }
+                }
+                curScreen = i;
+                if (range[0] < 0) {
+                    range[0] = curScreen;
+                }
+            }
+
+            range[1] = curScreen;
+        } else {
+            range[0] = -1;
+            range[1] = -1;
+        }
+    }
+
+    protected boolean shouldDrawChild(View child) {
+        return child.getAlpha() > 0 && child.getVisibility() == VISIBLE;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        int halfScreenSize = getViewportWidth() / 2;
+        // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
+        // Otherwise it is equal to the scaled overscroll position.
+        int screenCenter = mOverScrollX + halfScreenSize;
+
+        if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
+            // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
+            // set it for the next frame
+            mForceScreenScrolled = false;
+            screenScrolled(screenCenter);
+            mLastScreenCenter = screenCenter;
+        }
+
+        // Find out which screens are visible; as an optimization we only call draw on them
+        final int pageCount = getChildCount();
+        if (pageCount > 0) {
+            getVisiblePages(mTempVisiblePagesRange);
+            final int leftScreen = mTempVisiblePagesRange[0];
+            final int rightScreen = mTempVisiblePagesRange[1];
+            if (leftScreen != -1 && rightScreen != -1) {
+                final long drawingTime = getDrawingTime();
+                // Clip to the bounds
+                canvas.save();
+                canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
+                        getScrollY() + getBottom() - getTop());
+
+                // Draw all the children, leaving the drag view for last
+                for (int i = pageCount - 1; i >= 0; i--) {
+                    final View v = getPageAt(i);
+                    if (v == mDragView) continue;
+                    if (mForceDrawAllChildrenNextFrame ||
+                               (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
+                        drawChild(canvas, v, drawingTime);
+                    }
+                }
+                // Draw the drag view on top (if there is one)
+                if (mDragView != null) {
+                    drawChild(canvas, mDragView, drawingTime);
+                }
+
+                mForceDrawAllChildrenNextFrame = false;
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        int page = indexToPage(indexOfChild(child));
+        if (page != mCurrentPage || !mScroller.isFinished()) {
+            snapToPage(page);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        int focusablePage;
+        if (mNextPage != INVALID_PAGE) {
+            focusablePage = mNextPage;
+        } else {
+            focusablePage = mCurrentPage;
+        }
+        View v = getPageAt(focusablePage);
+        if (v != null) {
+            return v.requestFocus(direction, previouslyFocusedRect);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        // XXX-RTL: This will be fixed in a future CL
+        if (direction == View.FOCUS_LEFT) {
+            if (getCurrentPage() > 0) {
+                snapToPage(getCurrentPage() - 1);
+                return true;
+            }
+        } else if (direction == View.FOCUS_RIGHT) {
+            if (getCurrentPage() < getPageCount() - 1) {
+                snapToPage(getCurrentPage() + 1);
+                return true;
+            }
+        }
+        return super.dispatchUnhandledMove(focused, direction);
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        // XXX-RTL: This will be fixed in a future CL
+        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
+            getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
+        }
+        if (direction == View.FOCUS_LEFT) {
+            if (mCurrentPage > 0) {
+                getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+            }
+        } else if (direction == View.FOCUS_RIGHT){
+            if (mCurrentPage < getPageCount() - 1) {
+                getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+            }
+        }
+    }
+
+    /**
+     * If one of our descendant views decides that it could be focused now, only
+     * pass that along if it's on the current page.
+     *
+     * This happens when live folders requery, and if they're off page, they
+     * end up calling requestFocus, which pulls it on page.
+     */
+    @Override
+    public void focusableViewAvailable(View focused) {
+        View current = getPageAt(mCurrentPage);
+        View v = focused;
+        while (true) {
+            if (v == current) {
+                super.focusableViewAvailable(focused);
+                return;
+            }
+            if (v == this) {
+                return;
+            }
+            ViewParent parent = v.getParent();
+            if (parent instanceof View) {
+                v = (View)v.getParent();
+            } else {
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        if (disallowIntercept) {
+            // We need to make sure to cancel our long press if
+            // a scrollable widget takes over touch events
+            final View currentPage = getPageAt(mCurrentPage);
+            currentPage.cancelLongPress();
+        }
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+    }
+
+    /**
+     * Return true if a tap at (x, y) should trigger a flip to the previous page.
+     */
+    protected boolean hitsPreviousPage(float x, float y) {
+        int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
+        if (isLayoutRtl()) {
+            return (x > (getViewportOffsetX() + getViewportWidth() -
+                    offset + mPageSpacing));
+        }
+        return (x < getViewportOffsetX() + offset - mPageSpacing);
+    }
+
+    /**
+     * Return true if a tap at (x, y) should trigger a flip to the next page.
+     */
+    protected boolean hitsNextPage(float x, float y) {
+        int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
+        if (isLayoutRtl()) {
+            return (x < getViewportOffsetX() + offset - mPageSpacing);
+        }
+        return  (x > (getViewportOffsetX() + getViewportWidth() -
+                offset + mPageSpacing));
+    }
+
+    /** Returns whether x and y originated within the buffered viewport */
+    private boolean isTouchPointInViewportWithBuffer(int x, int y) {
+        mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
+                mViewport.right + mViewport.width() / 2, mViewport.bottom);
+        return mTmpRect.contains(x, y);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (DISABLE_TOUCH_INTERACTION) {
+            return false;
+        }
+
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onTouchEvent will be called and we do the actual
+         * scrolling there.
+         */
+        acquireVelocityTrackerAndAddMovement(ev);
+
+        // Skip touch handling if there are no pages to swipe
+        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
+
+        /*
+         * Shortcut the most recurring case: the user is in the dragging
+         * state and he is moving his finger.  We want to intercept this
+         * motion.
+         */
+        final int action = ev.getAction();
+        if ((action == MotionEvent.ACTION_MOVE) &&
+                (mTouchState == TOUCH_STATE_SCROLLING)) {
+            return true;
+        }
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+                if (mActivePointerId != INVALID_POINTER) {
+                    determineScrollingStart(ev);
+                }
+                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
+                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+                // i.e. fall through to the next case (don't break)
+                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
+                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
+                break;
+            }
+
+            case MotionEvent.ACTION_DOWN: {
+                final float x = ev.getX();
+                final float y = ev.getY();
+                // Remember location of down touch
+                mDownMotionX = x;
+                mDownMotionY = y;
+                mDownScrollX = getScrollX();
+                mLastMotionX = x;
+                mLastMotionY = y;
+                float[] p = mapPointFromViewToParent(this, x, y);
+                mParentDownMotionX = p[0];
+                mParentDownMotionY = p[1];
+                mLastMotionXRemainder = 0;
+                mTotalMotionX = 0;
+                mActivePointerId = ev.getPointerId(0);
+
+                /*
+                 * If being flinged and user touches the screen, initiate drag;
+                 * otherwise don't.  mScroller.isFinished should be false when
+                 * being flinged.
+                 */
+                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+                if (finishedScrolling) {
+                    mTouchState = TOUCH_STATE_REST;
+                    mScroller.abortAnimation();
+                } else {
+                    if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
+                        mTouchState = TOUCH_STATE_SCROLLING;
+                    } else {
+                        mTouchState = TOUCH_STATE_REST;
+                    }
+                }
+
+                // check if this can be the beginning of a tap on the side of the pages
+                // to scroll the current page
+                if (!DISABLE_TOUCH_SIDE_PAGES) {
+                    if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
+                        if (getChildCount() > 0) {
+                            if (hitsPreviousPage(x, y)) {
+                                mTouchState = TOUCH_STATE_PREV_PAGE;
+                            } else if (hitsNextPage(x, y)) {
+                                mTouchState = TOUCH_STATE_NEXT_PAGE;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                resetTouchState();
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                releaseVelocityTracker();
+                break;
+        }
+
+        /*
+         * The only time we want to intercept motion events is if we are in the
+         * drag mode.
+         */
+        return mTouchState != TOUCH_STATE_REST;
+    }
+
+    protected void determineScrollingStart(MotionEvent ev) {
+        determineScrollingStart(ev, 1.0f);
+    }
+
+    /*
+     * Determines if we should change the touch state to start scrolling after the
+     * user moves their touch point too far.
+     */
+    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+        // Disallow scrolling if we don't have a valid pointer index
+        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        if (pointerIndex == -1) return;
+
+        // Disallow scrolling if we started the gesture from outside the viewport
+        final float x = ev.getX(pointerIndex);
+        final float y = ev.getY(pointerIndex);
+        if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
+
+        final int xDiff = (int) Math.abs(x - mLastMotionX);
+        final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+        final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
+        boolean xPaged = xDiff > mPagingTouchSlop;
+        boolean xMoved = xDiff > touchSlop;
+        boolean yMoved = yDiff > touchSlop;
+
+        if (xMoved || xPaged || yMoved) {
+            if (mUsePagingTouchSlop ? xPaged : xMoved) {
+                // Scroll if the user moved far enough along the X axis
+                mTouchState = TOUCH_STATE_SCROLLING;
+                mTotalMotionX += Math.abs(mLastMotionX - x);
+                mLastMotionX = x;
+                mLastMotionXRemainder = 0;
+                mTouchX = getViewportOffsetX() + getScrollX();
+                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+                pageBeginMoving();
+            }
+        }
+    }
+
+    protected float getMaxScrollProgress() {
+        return 1.0f;
+    }
+
+    protected void cancelCurrentPageLongPress() {
+        if (mAllowLongPress) {
+            //mAllowLongPress = false;
+            // Try canceling the long press. It could also have been scheduled
+            // by a distant descendant, so use the mAllowLongPress flag to block
+            // everything
+            final View currentPage = getPageAt(mCurrentPage);
+            if (currentPage != null) {
+                currentPage.cancelLongPress();
+            }
+        }
+    }
+
+    protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
+        final int halfScreenSize = getViewportWidth() / 2;
+
+        screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter);
+        screenCenter = Math.max(halfScreenSize,  screenCenter);
+
+        return getScrollProgress(screenCenter, v, page);
+    }
+
+    protected float getScrollProgress(int screenCenter, View v, int page) {
+        final int halfScreenSize = getViewportWidth() / 2;
+
+        int totalDistance = v.getMeasuredWidth() + mPageSpacing;
+        int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
+
+        float scrollProgress = delta / (totalDistance * 1.0f);
+        scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
+        scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
+        return scrollProgress;
+    }
+
+    public int getScrollForPage(int index) {
+        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+            return 0;
+        } else {
+            return mPageScrolls[index];
+        }
+    }
+
+    // While layout transitions are occurring, a child's position may stray from its baseline
+    // position. This method returns the magnitude of this stray at any given time.
+    public int getLayoutTransitionOffsetForPage(int index) {
+        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+            return 0;
+        } else {
+            View child = getChildAt(index);
+            int scrollOffset = (getViewportWidth() - child.getMeasuredWidth()) / 2;
+            int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
+            return (int) (child.getX() - baselineX);
+        }
+    }
+
+    // This curve determines how the effect of scrolling over the limits of the page dimishes
+    // as the user pulls further and further from the bounds
+    private float overScrollInfluenceCurve(float f) {
+        f -= 1.0f;
+        return f * f * f + 1.0f;
+    }
+
+    protected void acceleratedOverScroll(float amount) {
+        int screenSize = getViewportWidth();
+
+        // We want to reach the max over scroll effect when the user has
+        // over scrolled half the size of the screen
+        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+        if (f == 0) return;
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+
+        int overScrollAmount = (int) Math.round(f * screenSize);
+        if (amount < 0) {
+            mOverScrollX = overScrollAmount;
+            super.scrollTo(0, getScrollY());
+        } else {
+            mOverScrollX = mMaxScrollX + overScrollAmount;
+            super.scrollTo(mMaxScrollX, getScrollY());
+        }
+        invalidate();
+    }
+
+    protected void dampedOverScroll(float amount) {
+        int screenSize = getViewportWidth();
+
+        float f = (amount / screenSize);
+
+        if (f == 0) return;
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+
+        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
+        if (amount < 0) {
+            mOverScrollX = overScrollAmount;
+            super.scrollTo(0, getScrollY());
+        } else {
+            mOverScrollX = mMaxScrollX + overScrollAmount;
+            super.scrollTo(mMaxScrollX, getScrollY());
+        }
+        invalidate();
+    }
+
+    protected void overScroll(float amount) {
+        dampedOverScroll(amount);
+    }
+
+    protected float maxOverScroll() {
+        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
+        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
+        float f = 1.0f;
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+        return OVERSCROLL_DAMP_FACTOR * f;
+    }
+
+    protected void enableFreeScroll() {
+        setEnableFreeScroll(true, -1);
+    }
+
+    protected void disableFreeScroll(int snapPage) {
+        setEnableFreeScroll(false, snapPage);
+    }
+
+    void updateFreescrollBounds() {
+        getOverviewModePages(mTempVisiblePagesRange);
+        if (isLayoutRtl()) {
+            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+        } else {
+            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+        }
+    }
+
+    private void setEnableFreeScroll(boolean freeScroll, int snapPage) {
+        mFreeScroll = freeScroll;
+
+        if (snapPage == -1) {
+            snapPage = getPageNearestToCenterOfScreen();
+        }
+
+        if (!mFreeScroll) {
+            snapToPage(snapPage);
+        } else {
+            updateFreescrollBounds();
+            getOverviewModePages(mTempVisiblePagesRange);
+            if (getCurrentPage() < mTempVisiblePagesRange[0]) {
+                setCurrentPage(mTempVisiblePagesRange[0]);
+            } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
+                setCurrentPage(mTempVisiblePagesRange[1]);
+            }
+        }
+
+        setEnableOverscroll(!freeScroll);
+    }
+
+    private void setEnableOverscroll(boolean enable) {
+        mAllowOverScroll = enable;
+    }
+
+    int getNearestHoverOverPageIndex() {
+        if (mDragView != null) {
+            int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
+                    + mDragView.getTranslationX());
+            getOverviewModePages(mTempVisiblePagesRange);
+            int minDistance = Integer.MAX_VALUE;
+            int minIndex = indexOfChild(mDragView);
+            for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
+                View page = getPageAt(i);
+                int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
+                int d = Math.abs(dragX - pageX);
+                if (d < minDistance) {
+                    minIndex = i;
+                    minDistance = d;
+                }
+            }
+            return minIndex;
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (DISABLE_TOUCH_INTERACTION) {
+            return false;
+        }
+
+        super.onTouchEvent(ev);
+
+        // Skip touch handling if there are no pages to swipe
+        if (getChildCount() <= 0) return super.onTouchEvent(ev);
+
+        acquireVelocityTrackerAndAddMovement(ev);
+
+        final int action = ev.getAction();
+
+        switch (action & MotionEvent.ACTION_MASK) {
+        case MotionEvent.ACTION_DOWN:
+            /*
+             * If being flinged and user touches, stop the fling. isFinished
+             * will be false if being flinged.
+             */
+            if (!mScroller.isFinished()) {
+                mScroller.abortAnimation();
+            }
+
+            // Remember where the motion event started
+            mDownMotionX = mLastMotionX = ev.getX();
+            mDownMotionY = mLastMotionY = ev.getY();
+            mDownScrollX = getScrollX();
+            float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+            mParentDownMotionX = p[0];
+            mParentDownMotionY = p[1];
+            mLastMotionXRemainder = 0;
+            mTotalMotionX = 0;
+            mActivePointerId = ev.getPointerId(0);
+
+            if (mTouchState == TOUCH_STATE_SCROLLING) {
+                pageBeginMoving();
+            }
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (mTouchState == TOUCH_STATE_SCROLLING) {
+                // Scroll to follow the motion event
+                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+
+                if (pointerIndex == -1) return true;
+
+                final float x = ev.getX(pointerIndex);
+                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
+
+                mTotalMotionX += Math.abs(deltaX);
+
+                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
+                // keep the remainder because we are actually testing if we've moved from the last
+                // scrolled position (which is discrete).
+                if (Math.abs(deltaX) >= 1.0f) {
+                    mTouchX += deltaX;
+                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+                    if (!mDeferScrollUpdate) {
+                        scrollBy((int) deltaX, 0);
+                        if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
+                    } else {
+                        invalidate();
+                    }
+                    mLastMotionX = x;
+                    mLastMotionXRemainder = deltaX - (int) deltaX;
+                } else {
+                    awakenScrollBars();
+                }
+            } else if (mTouchState == TOUCH_STATE_REORDERING) {
+                // Update the last motion position
+                mLastMotionX = ev.getX();
+                mLastMotionY = ev.getY();
+
+                // Update the parent down so that our zoom animations take this new movement into
+                // account
+                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+                mParentDownMotionX = pt[0];
+                mParentDownMotionY = pt[1];
+                updateDragViewTranslationDuringDrag();
+
+                // Find the closest page to the touch point
+                final int dragViewIndex = indexOfChild(mDragView);
+
+                // Change the drag view if we are hovering over the drop target
+                boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
+                        (int) mParentDownMotionX, (int) mParentDownMotionY);
+                setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
+
+                if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
+                if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
+                if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
+                if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
+
+                final int pageUnderPointIndex = getNearestHoverOverPageIndex();
+                if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) &&
+                        !isHoveringOverDelete) {
+                    mTempVisiblePagesRange[0] = 0;
+                    mTempVisiblePagesRange[1] = getPageCount() - 1;
+                    getOverviewModePages(mTempVisiblePagesRange);
+                    if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
+                            pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
+                            pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
+                        mSidePageHoverIndex = pageUnderPointIndex;
+                        mSidePageHoverRunnable = new Runnable() {
+                            @Override
+                            public void run() {
+                                // Setup the scroll to the correct page before we swap the views
+                                snapToPage(pageUnderPointIndex);
+
+                                // For each of the pages between the paged view and the drag view,
+                                // animate them from the previous position to the new position in
+                                // the layout (as a result of the drag view moving in the layout)
+                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
+                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
+                                        dragViewIndex + 1 : pageUnderPointIndex;
+                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
+                                        dragViewIndex - 1 : pageUnderPointIndex;
+                                for (int i = lowerIndex; i <= upperIndex; ++i) {
+                                    View v = getChildAt(i);
+                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
+                                    // drag view all subsequent views to pageUnderPointIndex will
+                                    // shift down.
+                                    int oldX = getViewportOffsetX() + getChildOffset(i);
+                                    int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
+
+                                    // Animate the view translation from its old position to its new
+                                    // position
+                                    AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
+                                    if (anim != null) {
+                                        anim.cancel();
+                                    }
+
+                                    v.setTranslationX(oldX - newX);
+                                    anim = new AnimatorSet();
+                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
+                                    anim.playTogether(
+                                            ObjectAnimator.ofFloat(v, "translationX", 0f));
+                                    anim.start();
+                                    v.setTag(anim);
+                                }
+
+                                removeView(mDragView);
+                                onRemoveView(mDragView, false);
+                                addView(mDragView, pageUnderPointIndex);
+                                onAddView(mDragView, pageUnderPointIndex);
+                                mSidePageHoverIndex = -1;
+                                mPageIndicator.setActiveMarker(getNextPage());
+                            }
+                        };
+                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
+                    }
+                } else {
+                    removeCallbacks(mSidePageHoverRunnable);
+                    mSidePageHoverIndex = -1;
+                }
+            } else {
+                determineScrollingStart(ev);
+            }
+            break;
+
+        case MotionEvent.ACTION_UP:
+            if (mTouchState == TOUCH_STATE_SCROLLING) {
+                final int activePointerId = mActivePointerId;
+                final int pointerIndex = ev.findPointerIndex(activePointerId);
+                final float x = ev.getX(pointerIndex);
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+                final int deltaX = (int) (x - mDownMotionX);
+                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
+                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
+                        SIGNIFICANT_MOVE_THRESHOLD;
+
+                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
+
+                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
+                        Math.abs(velocityX) > mFlingThresholdVelocity;
+
+                if (!mFreeScroll) {
+                    // In the case that the page is moved far to one direction and then is flung
+                    // in the opposite direction, we use a threshold to determine whether we should
+                    // just return to the starting page, or if we should skip one further.
+                    boolean returnToOriginalPage = false;
+                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+                            Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+                        returnToOriginalPage = true;
+                    }
+
+                    int finalPage;
+                    // We give flings precedence over large moves, which is why we short-circuit our
+                    // test for a large move if a fling has been registered. That is, a large
+                    // move to the left and fling to the right will register as a fling to the right.
+                    final boolean isRtl = isLayoutRtl();
+                    boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;
+                    boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;
+                    if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
+                            (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
+                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+                        snapToPageWithVelocity(finalPage, velocityX);
+                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
+                            (isFling && isVelocityXLeft)) &&
+                            mCurrentPage < getChildCount() - 1) {
+                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+                        snapToPageWithVelocity(finalPage, velocityX);
+                    } else {
+                        snapToDestination();
+                    }            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
+                    // at this point we have not moved beyond the touch slop
+                    // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+                    // we can just page
+                    int nextPage = Math.max(0, mCurrentPage - 1);
+                    if (nextPage != mCurrentPage) {
+                        snapToPage(nextPage);
+                    } else {
+                        snapToDestination();
+                    }
+                } else {
+                    if (!mScroller.isFinished()) {
+                        mScroller.abortAnimation();
+                    }
+
+                    float scaleX = getScaleX();
+                    int vX = (int) (-velocityX * scaleX);
+                    int initialScrollX = (int) (getScrollX() * scaleX);
+
+                    mScroller.fling(initialScrollX,
+                            getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
+                    invalidate();
+                }
+            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
+                // at this point we have not moved beyond the touch slop
+                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+                // we can just page
+                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
+                if (nextPage != mCurrentPage) {
+                    snapToPage(nextPage);
+                } else {
+                    snapToDestination();
+                }
+            } else if (mTouchState == TOUCH_STATE_REORDERING) {
+                // Update the last motion position
+                mLastMotionX = ev.getX();
+                mLastMotionY = ev.getY();
+
+                // Update the parent down so that our zoom animations take this new movement into
+                // account
+                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+                mParentDownMotionX = pt[0];
+                mParentDownMotionY = pt[1];
+                updateDragViewTranslationDuringDrag();
+                boolean handledFling = false;
+                if (!DISABLE_FLING_TO_DELETE) {
+                    // Check the velocity and see if we are flinging-to-delete
+                    PointF flingToDeleteVector = isFlingingToDelete();
+                    if (flingToDeleteVector != null) {
+                        onFlingToDelete(flingToDeleteVector);
+                        handledFling = true;
+                    }
+                }
+                if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
+                        (int) mParentDownMotionY)) {
+                    onDropToDelete();
+                }
+            } else {
+                if (!mCancelTap) {
+                    onUnhandledTap(ev);
+                }
+            }
+
+            // Remove the callback to wait for the side page hover timeout
+            removeCallbacks(mSidePageHoverRunnable);
+            // End any intermediate reordering states
+            resetTouchState();
+            break;
+
+        case MotionEvent.ACTION_CANCEL:
+            if (mTouchState == TOUCH_STATE_SCROLLING) {
+                snapToDestination();
+            }
+            resetTouchState();
+            break;
+
+        case MotionEvent.ACTION_POINTER_UP:
+            onSecondaryPointerUp(ev);
+            releaseVelocityTracker();
+            break;
+        }
+
+        return true;
+    }
+
+    public void onFlingToDelete(View v) {}
+    public void onRemoveView(View v, boolean deletePermanently) {}
+    public void onRemoveViewAnimationCompleted() {}
+    public void onAddView(View v, int index) {}
+
+    private void resetTouchState() {
+        releaseVelocityTracker();
+        endReordering();
+        mCancelTap = false;
+        mTouchState = TOUCH_STATE_REST;
+        mActivePointerId = INVALID_POINTER;
+    }
+
+    protected void onUnhandledTap(MotionEvent ev) {
+        ((Launcher) getContext()).onClick(this);
+    }
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_SCROLL: {
+                    // Handle mouse (or ext. device) by shifting the page depending on the scroll
+                    final float vscroll;
+                    final float hscroll;
+                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
+                        vscroll = 0;
+                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+                    } else {
+                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+                    }
+                    if (hscroll != 0 || vscroll != 0) {
+                        boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0)
+                                                         : (hscroll > 0 || vscroll > 0);
+                        if (isForwardScroll) {
+                            scrollRight();
+                        } else {
+                            scrollLeft();
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+        return super.onGenericMotionEvent(event);
+    }
+
+    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+    }
+
+    private void releaseVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.clear();
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+        final int pointerId = ev.getPointerId(pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            // TODO: Make this decision more intelligent.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+            mLastMotionY = ev.getY(newPointerIndex);
+            mLastMotionXRemainder = 0;
+            mActivePointerId = ev.getPointerId(newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        int page = indexToPage(indexOfChild(child));
+        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
+            snapToPage(page);
+        }
+    }
+
+    protected int getChildWidth(int index) {
+        return getPageAt(index).getMeasuredWidth();
+    }
+
+    int getPageNearestToPoint(float x) {
+        int index = 0;
+        for (int i = 0; i < getChildCount(); ++i) {
+            if (x < getChildAt(i).getRight() - getScrollX()) {
+                return index;
+            } else {
+                index++;
+            }
+        }
+        return Math.min(index, getChildCount() - 1);
+    }
+
+    int getPageNearestToCenterOfScreen() {
+        int minDistanceFromScreenCenter = Integer.MAX_VALUE;
+        int minDistanceFromScreenCenterIndex = -1;
+        int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; ++i) {
+            View layout = (View) getPageAt(i);
+            int childWidth = layout.getMeasuredWidth();
+            int halfChildWidth = (childWidth / 2);
+            int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
+            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
+                minDistanceFromScreenCenter = distanceFromScreenCenter;
+                minDistanceFromScreenCenterIndex = i;
+            }
+        }
+        return minDistanceFromScreenCenterIndex;
+    }
+
+    protected void snapToDestination() {
+        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+    }
+
+    private static class ScrollInterpolator implements Interpolator {
+        public ScrollInterpolator() {
+        }
+
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t*t*t*t*t + 1;
+        }
+    }
+
+    // We want the duration of the page snap animation to be influenced by the distance that
+    // the screen has to travel, however, we don't want this duration to be effected in a
+    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+    // of travel has on the overall snap duration.
+    float distanceInfluenceForSnapDuration(float f) {
+        f -= 0.5f; // center the values about 0.
+        f *= 0.3f * Math.PI / 2.0f;
+        return (float) Math.sin(f);
+    }
+
+    protected void snapToPageWithVelocity(int whichPage, int velocity) {
+        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+        int halfScreenSize = getViewportWidth() / 2;
+
+        final int newX = getScrollForPage(whichPage);
+        int delta = newX - mUnboundedScrollX;
+        int duration = 0;
+
+        if (Math.abs(velocity) < mMinFlingVelocity) {
+            // If the velocity is low enough, then treat this more as an automatic page advance
+            // as opposed to an apparent physical response to flinging
+            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+            return;
+        }
+
+        // Here we compute a "distance" that will be used in the computation of the overall
+        // snap duration. This is a function of the actual distance that needs to be traveled;
+        // we keep this value close to half screen size in order to reduce the variance in snap
+        // duration as a function of the distance the page needs to travel.
+        float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
+        float distance = halfScreenSize + halfScreenSize *
+                distanceInfluenceForSnapDuration(distanceRatio);
+
+        velocity = Math.abs(velocity);
+        velocity = Math.max(mMinSnapVelocity, velocity);
+
+        // we want the page's snap velocity to approximately match the velocity at which the
+        // user flings, so we scale the duration by a value near to the derivative of the scroll
+        // interpolator at zero, ie. 5. We use 4 to make it a little slower.
+        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+
+        snapToPage(whichPage, delta, duration);
+    }
+
+    protected void snapToPage(int whichPage) {
+        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+    }
+
+    protected void snapToPageImmediately(int whichPage) {
+        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+    }
+
+    protected void snapToPage(int whichPage, int duration) {
+        snapToPage(whichPage, duration, false);
+    }
+
+    protected void snapToPage(int whichPage, int duration, boolean immediate) {
+        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
+
+        int newX = getScrollForPage(whichPage);
+        final int sX = mUnboundedScrollX;
+        final int delta = newX - sX;
+        snapToPage(whichPage, delta, duration, immediate);
+    }
+
+    protected void snapToPage(int whichPage, int delta, int duration) {
+        snapToPage(whichPage, delta, duration, false);
+    }
+
+    protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
+        mNextPage = whichPage;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null && whichPage != mCurrentPage &&
+                focusedChild == getPageAt(mCurrentPage)) {
+            focusedChild.clearFocus();
+        }
+
+        sendScrollAccessibilityEvent();
+
+        pageBeginMoving();
+        awakenScrollBars(duration);
+        if (immediate) {
+            duration = 0;
+        } else if (duration == 0) {
+            duration = Math.abs(delta);
+        }
+
+        if (!mScroller.isFinished()) {
+            mScroller.abortAnimation();
+        }
+        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
+
+        notifyPageSwitchListener();
+
+        // Trigger a compute() to finish switching pages if necessary
+        if (immediate) {
+            computeScroll();
+        }
+
+        // Defer loading associated pages until the scroll settles
+        mDeferLoadAssociatedPagesUntilScrollCompletes = true;
+
+        mForceScreenScrolled = true;
+        invalidate();
+    }
+
+    public void scrollLeft() {
+        if (getNextPage() > 0) snapToPage(getNextPage() - 1);
+    }
+
+    public void scrollRight() {
+        if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
+    }
+
+    public int getPageForView(View v) {
+        int result = -1;
+        if (v != null) {
+            ViewParent vp = v.getParent();
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                if (vp == getPageAt(i)) {
+                    return i;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @return True is long presses are still allowed for the current touch
+     */
+    public boolean allowLongPress() {
+        return mAllowLongPress;
+    }
+
+    @Override
+    public boolean performLongClick() {
+        mCancelTap = true;
+        return super.performLongClick();
+    }
+
+    /**
+     * Set true to allow long-press events to be triggered, usually checked by
+     * {@link Launcher} to accept or block dpad-initiated long-presses.
+     */
+    public void setAllowLongPress(boolean allowLongPress) {
+        mAllowLongPress = allowLongPress;
+    }
+
+    public static class SavedState extends BaseSavedState {
+        int currentPage = -1;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            currentPage = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(currentPage);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    protected void loadAssociatedPages(int page) {
+        loadAssociatedPages(page, false);
+    }
+    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
+        if (mContentIsRefreshable) {
+            final int count = getChildCount();
+            if (page < count) {
+                int lowerPageBound = getAssociatedLowerPageBound(page);
+                int upperPageBound = getAssociatedUpperPageBound(page);
+                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
+                        + upperPageBound);
+                // First, clear any pages that should no longer be loaded
+                for (int i = 0; i < count; ++i) {
+                    Page layout = (Page) getPageAt(i);
+                    if ((i < lowerPageBound) || (i > upperPageBound)) {
+                        if (layout.getPageChildCount() > 0) {
+                            layout.removeAllViewsOnPage();
+                        }
+                        mDirtyPageContent.set(i, true);
+                    }
+                }
+                // Next, load any new pages
+                for (int i = 0; i < count; ++i) {
+                    if ((i != page) && immediateAndOnly) {
+                        continue;
+                    }
+                    if (lowerPageBound <= i && i <= upperPageBound) {
+                        if (mDirtyPageContent.get(i)) {
+                            syncPageItems(i, (i == page) && immediateAndOnly);
+                            mDirtyPageContent.set(i, false);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected int getAssociatedLowerPageBound(int page) {
+        return Math.max(0, page - 1);
+    }
+    protected int getAssociatedUpperPageBound(int page) {
+        final int count = getChildCount();
+        return Math.min(page + 1, count - 1);
+    }
+
+    /**
+     * This method is called ONLY to synchronize the number of pages that the paged view has.
+     * To actually fill the pages with information, implement syncPageItems() below.  It is
+     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
+     * and therefore, individual page items do not need to be updated in this method.
+     */
+    public abstract void syncPages();
+
+    /**
+     * This method is called to synchronize the items that are on a particular page.  If views on
+     * the page can be reused, then they should be updated within this method.
+     */
+    public abstract void syncPageItems(int page, boolean immediate);
+
+    protected void invalidatePageData() {
+        invalidatePageData(-1, false);
+    }
+    protected void invalidatePageData(int currentPage) {
+        invalidatePageData(currentPage, false);
+    }
+    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
+        if (!mIsDataReady) {
+            return;
+        }
+
+        if (mContentIsRefreshable) {
+            // Force all scrolling-related behavior to end
+            mScroller.forceFinished(true);
+            mNextPage = INVALID_PAGE;
+
+            // Update all the pages
+            syncPages();
+
+            // We must force a measure after we've loaded the pages to update the content width and
+            // to determine the full scroll width
+            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+
+            // Set a new page as the current page if necessary
+            if (currentPage > -1) {
+                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
+            }
+
+            // Mark each of the pages as dirty
+            final int count = getChildCount();
+            mDirtyPageContent.clear();
+            for (int i = 0; i < count; ++i) {
+                mDirtyPageContent.add(true);
+            }
+
+            // Load any pages that are necessary for the current window of views
+            loadAssociatedPages(mCurrentPage, immediateAndOnly);
+            requestLayout();
+        }
+        if (isPageMoving()) {
+            // If the page is moving, then snap it to the final position to ensure we don't get
+            // stuck between pages
+            snapToDestination();
+        }
+    }
+
+    // Animate the drag view back to the original position
+    void animateDragViewToOriginalPosition() {
+        if (mDragView != null) {
+            AnimatorSet anim = new AnimatorSet();
+            anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
+            anim.playTogether(
+                    ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
+                    ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
+                    ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
+                    ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onPostReorderingAnimationCompleted();
+                }
+            });
+            anim.start();
+        }
+    }
+
+    protected void onStartReordering() {
+        // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
+        mTouchState = TOUCH_STATE_REORDERING;
+        mIsReordering = true;
+
+        // We must invalidate to trigger a redraw to update the layers such that the drag view
+        // is always drawn on top
+        invalidate();
+    }
+
+    private void onPostReorderingAnimationCompleted() {
+        // Trigger the callback when reordering has settled
+        --mPostReorderingPreZoomInRemainingAnimationCount;
+        if (mPostReorderingPreZoomInRunnable != null &&
+                mPostReorderingPreZoomInRemainingAnimationCount == 0) {
+            mPostReorderingPreZoomInRunnable.run();
+            mPostReorderingPreZoomInRunnable = null;
+        }
+    }
+
+    protected void onEndReordering() {
+        mIsReordering = false;
+    }
+
+    public boolean startReordering(View v) {
+        int dragViewIndex = indexOfChild(v);
+
+        if (mTouchState != TOUCH_STATE_REST) return false;
+
+        mTempVisiblePagesRange[0] = 0;
+        mTempVisiblePagesRange[1] = getPageCount() - 1;
+        getOverviewModePages(mTempVisiblePagesRange);
+        mReorderingStarted = true;
+
+        // Check if we are within the reordering range
+        if (mTempVisiblePagesRange[0] <= dragViewIndex &&
+            dragViewIndex <= mTempVisiblePagesRange[1]) {
+            // Find the drag view under the pointer
+            mDragView = getChildAt(dragViewIndex);
+            mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
+            mDragViewBaselineLeft = mDragView.getLeft();
+            disableFreeScroll(-1);
+            onStartReordering();
+            return true;
+        }
+        return false;
+    }
+
+    boolean isReordering(boolean testTouchState) {
+        boolean state = mIsReordering;
+        if (testTouchState) {
+            state &= (mTouchState == TOUCH_STATE_REORDERING);
+        }
+        return state;
+    }
+    void endReordering() {
+        // For simplicity, we call endReordering sometimes even if reordering was never started.
+        // In that case, we don't want to do anything.
+        if (!mReorderingStarted) return;
+        mReorderingStarted = false;
+
+        // If we haven't flung-to-delete the current child, then we just animate the drag view
+        // back into position
+        final Runnable onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                onEndReordering();
+            }
+        };
+        if (!mDeferringForDelete) {
+            mPostReorderingPreZoomInRunnable = new Runnable() {
+                public void run() {
+                    onCompleteRunnable.run();
+                    enableFreeScroll();
+                };
+            };
+
+            mPostReorderingPreZoomInRemainingAnimationCount =
+                    NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
+            // Snap to the current page
+            snapToPage(indexOfChild(mDragView), 0);
+            // Animate the drag view back to the front position
+            animateDragViewToOriginalPosition();
+        } else {
+            // Handled in post-delete-animation-callbacks
+        }
+    }
+
+    /*
+     * Flinging to delete - IN PROGRESS
+     */
+    private PointF isFlingingToDelete() {
+        ViewConfiguration config = ViewConfiguration.get(getContext());
+        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+                    mVelocityTracker.getYVelocity());
+            PointF upVec = new PointF(0f, -1f);
+            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+                    (vel.length() * upVec.length()));
+            if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
+                return vel;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Creates an animation from the current drag view along its current velocity vector.
+     * For this animation, the alpha runs for a fixed duration and we update the position
+     * progressively.
+     */
+    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+        private View mDragView;
+        private PointF mVelocity;
+        private Rect mFrom;
+        private long mPrevTime;
+        private float mFriction;
+
+        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+        public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
+                long startTime, float friction) {
+            mDragView = dragView;
+            mVelocity = vel;
+            mFrom = from;
+            mPrevTime = startTime;
+            mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float t = ((Float) animation.getAnimatedValue()).floatValue();
+            long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+            mDragView.setTranslationX(mFrom.left);
+            mDragView.setTranslationY(mFrom.top);
+            mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+            mVelocity.x *= mFriction;
+            mVelocity.y *= mFriction;
+            mPrevTime = curTime;
+        }
+    };
+
+    private static final int ANIM_TAG_KEY = 100;
+
+    private Runnable createPostDeleteAnimationRunnable(final View dragView) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                int dragViewIndex = indexOfChild(dragView);
+
+                // For each of the pages around the drag view, animate them from the previous
+                // position to the new position in the layout (as a result of the drag view moving
+                // in the layout)
+                // NOTE: We can make an assumption here because we have side-bound pages that we
+                //       will always have pages to animate in from the left
+                getOverviewModePages(mTempVisiblePagesRange);
+                boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
+                boolean slideFromLeft = (isLastWidgetPage ||
+                        dragViewIndex > mTempVisiblePagesRange[0]);
+
+                // Setup the scroll to the correct page before we swap the views
+                if (slideFromLeft) {
+                    snapToPageImmediately(dragViewIndex - 1);
+                }
+
+                int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
+                int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
+                int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
+                int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
+                ArrayList<Animator> animations = new ArrayList<Animator>();
+                for (int i = lowerIndex; i <= upperIndex; ++i) {
+                    View v = getChildAt(i);
+                    // dragViewIndex < pageUnderPointIndex, so after we remove the
+                    // drag view all subsequent views to pageUnderPointIndex will
+                    // shift down.
+                    int oldX = 0;
+                    int newX = 0;
+                    if (slideFromLeft) {
+                        if (i == 0) {
+                            // Simulate the page being offscreen with the page spacing
+                            oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
+                                    - mPageSpacing;
+                        } else {
+                            oldX = getViewportOffsetX() + getChildOffset(i - 1);
+                        }
+                        newX = getViewportOffsetX() + getChildOffset(i);
+                    } else {
+                        oldX = getChildOffset(i) - getChildOffset(i - 1);
+                        newX = 0;
+                    }
+
+                    // Animate the view translation from its old position to its new
+                    // position
+                    AnimatorSet anim = (AnimatorSet) v.getTag();
+                    if (anim != null) {
+                        anim.cancel();
+                    }
+
+                    // Note: Hacky, but we want to skip any optimizations to not draw completely
+                    // hidden views
+                    v.setAlpha(Math.max(v.getAlpha(), 0.01f));
+                    v.setTranslationX(oldX - newX);
+                    anim = new AnimatorSet();
+                    anim.playTogether(
+                            ObjectAnimator.ofFloat(v, "translationX", 0f),
+                            ObjectAnimator.ofFloat(v, "alpha", 1f));
+                    animations.add(anim);
+                    v.setTag(ANIM_TAG_KEY, anim);
+                }
+
+                AnimatorSet slideAnimations = new AnimatorSet();
+                slideAnimations.playTogether(animations);
+                slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
+                slideAnimations.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mDeferringForDelete = false;
+                        onEndReordering();
+                        onRemoveViewAnimationCompleted();
+                    }
+                });
+                slideAnimations.start();
+
+                removeView(dragView);
+                onRemoveView(dragView, true);
+            }
+        };
+    }
+
+    public void onFlingToDelete(PointF vel) {
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+        // NOTE: Because it takes time for the first frame of animation to actually be
+        // called and we expect the animation to be a continuation of the fling, we have
+        // to account for the time that has elapsed since the fling finished.  And since
+        // we don't have a startDelay, we will always get call to update when we call
+        // start() (which we want to ignore).
+        final TimeInterpolator tInterpolator = new TimeInterpolator() {
+            private int mCount = -1;
+            private long mStartTime;
+            private float mOffset;
+            /* Anonymous inner class ctor */ {
+                mStartTime = startTime;
+            }
+
+            @Override
+            public float getInterpolation(float t) {
+                if (mCount < 0) {
+                    mCount++;
+                } else if (mCount == 0) {
+                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+                            mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
+                    mCount++;
+                }
+                return Math.min(1f, mOffset + t);
+            }
+        };
+
+        final Rect from = new Rect();
+        final View dragView = mDragView;
+        from.left = (int) dragView.getTranslationX();
+        from.top = (int) dragView.getTranslationY();
+        AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
+                from, startTime, FLING_TO_DELETE_FRICTION);
+
+        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
+
+        // Create and start the animation
+        ValueAnimator mDropAnim = new ValueAnimator();
+        mDropAnim.setInterpolator(tInterpolator);
+        mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
+        mDropAnim.setFloatValues(0f, 1f);
+        mDropAnim.addUpdateListener(updateCb);
+        mDropAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                onAnimationEndRunnable.run();
+            }
+        });
+        mDropAnim.start();
+        mDeferringForDelete = true;
+    }
+
+    /* Drag to delete */
+    private boolean isHoveringOverDeleteDropTarget(int x, int y) {
+        if (mDeleteDropTarget != null) {
+            mAltTmpRect.set(0, 0, 0, 0);
+            View parent = (View) mDeleteDropTarget.getParent();
+            if (parent != null) {
+                parent.getGlobalVisibleRect(mAltTmpRect);
+            }
+            mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
+            mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
+            return mTmpRect.contains(x, y);
+        }
+        return false;
+    }
+
+    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
+
+    private void onDropToDelete() {
+        final View dragView = mDragView;
+
+        final float toScale = 0f;
+        final float toAlpha = 0f;
+
+        // Create and start the complex animation
+        ArrayList<Animator> animations = new ArrayList<Animator>();
+        AnimatorSet motionAnim = new AnimatorSet();
+        motionAnim.setInterpolator(new DecelerateInterpolator(2));
+        motionAnim.playTogether(
+                ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
+                ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
+        animations.add(motionAnim);
+
+        AnimatorSet alphaAnim = new AnimatorSet();
+        alphaAnim.setInterpolator(new LinearInterpolator());
+        alphaAnim.playTogether(
+                ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
+        animations.add(alphaAnim);
+
+        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
+
+        AnimatorSet anim = new AnimatorSet();
+        anim.playTogether(animations);
+        anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
+        anim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                onAnimationEndRunnable.run();
+            }
+        });
+        anim.start();
+
+        mDeferringForDelete = true;
+    }
+
+    /* Accessibility */
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setScrollable(getPageCount() > 1);
+        if (getCurrentPage() < getPageCount() - 1) {
+            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+        }
+        if (getCurrentPage() > 0) {
+            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+        }
+    }
+
+    @Override
+    public void sendAccessibilityEvent(int eventType) {
+        // Don't let the view send real scroll events.
+        if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            super.sendAccessibilityEvent(eventType);
+        }
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(true);
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (super.performAccessibilityAction(action, arguments)) {
+            return true;
+        }
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+                if (getCurrentPage() < getPageCount() - 1) {
+                    scrollRight();
+                    return true;
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+                if (getCurrentPage() > 0) {
+                    scrollLeft();
+                    return true;
+                }
+            } break;
+        }
+        return false;
+    }
+
+    protected String getCurrentPageDescription() {
+        return String.format(getContext().getString(R.string.default_scroll_format),
+                getNextPage() + 1, getChildCount());
+    }
+
+    @Override
+    public boolean onHoverEvent(android.view.MotionEvent event) {
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewCellLayout.java b/src/com/android/launcher3/PagedViewCellLayout.java
new file mode 100644
index 0000000..2d9e10b
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewCellLayout.java
@@ -0,0 +1,492 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+/**
+ * An abstraction of the original CellLayout which supports laying out items
+ * which span multiple cells into a grid-like layout.  Also supports dimming
+ * to give a preview of its contents.
+ */
+public class PagedViewCellLayout extends ViewGroup implements Page {
+    static final String TAG = "PagedViewCellLayout";
+
+    private int mCellCountX;
+    private int mCellCountY;
+    private int mOriginalCellWidth;
+    private int mOriginalCellHeight;
+    private int mCellWidth;
+    private int mCellHeight;
+    private int mOriginalWidthGap;
+    private int mOriginalHeightGap;
+    private int mWidthGap;
+    private int mHeightGap;
+    protected PagedViewCellLayoutChildren mChildren;
+
+    public PagedViewCellLayout(Context context) {
+        this(context, null);
+    }
+
+    public PagedViewCellLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setAlwaysDrawnWithCacheEnabled(false);
+
+        // setup default cell parameters
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mOriginalCellWidth = mCellWidth = grid.cellWidthPx;
+        mOriginalCellHeight = mCellHeight = grid.cellHeightPx;
+        mCellCountX = (int) grid.numColumns;
+        mCellCountY = (int) grid.numRows;
+        mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
+
+        mChildren = new PagedViewCellLayoutChildren(context);
+        mChildren.setCellDimensions(mCellWidth, mCellHeight);
+        mChildren.setGap(mWidthGap, mHeightGap);
+
+        addView(mChildren);
+    }
+
+    public int getCellWidth() {
+        return mCellWidth;
+    }
+
+    public int getCellHeight() {
+        return mCellHeight;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
+    public boolean addViewToCellLayout(View child, int index, int childId,
+            PagedViewCellLayout.LayoutParams params) {
+        final PagedViewCellLayout.LayoutParams lp = params;
+
+        // Generate an id for each view, this assumes we have at most 256x256 cells
+        // per workspace screen
+        if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
+                lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
+            // If the horizontal or vertical span is set to -1, it is taken to
+            // mean that it spans the extent of the CellLayout
+            if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
+            if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
+
+            child.setId(childId);
+            mChildren.addView(child, index, lp);
+
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeAllViewsOnPage() {
+        mChildren.removeAllViews();
+        setLayerType(LAYER_TYPE_NONE, null);
+    }
+
+    @Override
+    public void removeViewOnPageAt(int index) {
+        mChildren.removeViewAt(index);
+    }
+
+    /**
+     * Clears all the key listeners for the individual icons.
+     */
+    public void resetChildrenOnKeyListeners() {
+        int childCount = mChildren.getChildCount();
+        for (int j = 0; j < childCount; ++j) {
+            mChildren.getChildAt(j).setOnKeyListener(null);
+        }
+    }
+
+    @Override
+    public int getPageChildCount() {
+        return mChildren.getChildCount();
+    }
+
+    public PagedViewCellLayoutChildren getChildrenLayout() {
+        return mChildren;
+    }
+
+    @Override
+    public View getChildOnPageAt(int i) {
+        return mChildren.getChildAt(i);
+    }
+
+    @Override
+    public int indexOfChildOnPage(View v) {
+        return mChildren.indexOfChild(v);
+    }
+
+    public int getCellCountX() {
+        return mCellCountX;
+    }
+
+    public int getCellCountY() {
+        return mCellCountY;
+    }
+
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+        }
+
+        int numWidthGaps = mCellCountX - 1;
+        int numHeightGaps = mCellCountY - 1;
+
+        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
+            int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
+            int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
+            int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
+            int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
+            mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0;
+            mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0;
+
+            mChildren.setGap(mWidthGap, mHeightGap);
+        } else {
+            mWidthGap = mOriginalWidthGap;
+            mHeightGap = mOriginalHeightGap;
+        }
+
+        // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
+        int newWidth = widthSpecSize;
+        int newHeight = heightSpecSize;
+        if (widthSpecMode == MeasureSpec.AT_MOST) {
+            newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
+                ((mCellCountX - 1) * mWidthGap);
+            newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
+                ((mCellCountY - 1) * mHeightGap);
+            setMeasuredDimension(newWidth, newHeight);
+        }
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            int childWidthMeasureSpec =
+                MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
+                        getPaddingRight(), MeasureSpec.EXACTLY);
+            int childheightMeasureSpec =
+                MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
+                        getPaddingBottom(), MeasureSpec.EXACTLY);
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+        }
+
+        setMeasuredDimension(newWidth, newHeight);
+    }
+
+    int getContentWidth() {
+        return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
+    }
+
+    int getContentHeight() {
+        if (mCellCountY > 0) {
+            return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
+        }
+        return 0;
+    }
+
+    int getWidthBeforeFirstLayout() {
+        if (mCellCountX > 0) {
+            return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
+        }
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            child.layout(getPaddingLeft(), getPaddingTop(),
+                r - l - getPaddingRight(), b - t - getPaddingBottom());
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean result = super.onTouchEvent(event);
+        int count = getPageChildCount();
+        if (count > 0) {
+            // We only intercept the touch if we are tapping in empty space after the final row
+            View child = getChildOnPageAt(count - 1);
+            int bottom = child.getBottom();
+            int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
+            if (numRows < getCellCountY()) {
+                // Add a little bit of buffer if there is room for another row
+                bottom += mCellHeight / 2;
+            }
+            result = result || (event.getY() < bottom);
+        }
+        return result;
+    }
+
+    public void enableCenteredContent(boolean enabled) {
+        mChildren.enableCenteredContent(enabled);
+    }
+
+    @Override
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        mChildren.setChildrenDrawingCacheEnabled(enabled);
+    }
+
+    public void setCellCount(int xCount, int yCount) {
+        mCellCountX = xCount;
+        mCellCountY = yCount;
+        requestLayout();
+    }
+
+    public void setGap(int widthGap, int heightGap) {
+        mOriginalWidthGap = mWidthGap = widthGap;
+        mOriginalHeightGap = mHeightGap = heightGap;
+        mChildren.setGap(widthGap, heightGap);
+    }
+
+    public int[] getCellCountForDimensions(int width, int height) {
+        // Always assume we're working with the smallest span to make sure we
+        // reserve enough space in both orientations
+        int smallerSize = Math.min(mCellWidth, mCellHeight);
+
+        // Always round up to next largest cell
+        int spanX = (width + smallerSize) / smallerSize;
+        int spanY = (height + smallerSize) / smallerSize;
+
+        return new int[] { spanX, spanY };
+    }
+
+    /**
+     * Start dragging the specified child
+     *
+     * @param child The child that is being dragged
+     */
+    void onDragChild(View child) {
+        PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+        lp.isDragging = true;
+    }
+
+    /**
+     * Estimates the number of cells that the specified width would take up.
+     */
+    public int estimateCellHSpan(int width) {
+        // We don't show the next/previous pages any more, so we use the full width, minus the
+        // padding
+        int availWidth = width - (getPaddingLeft() + getPaddingRight());
+
+        // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
+        int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
+
+        // We don't do anything fancy to determine if we squeeze another row in.
+        return n;
+    }
+
+    /**
+     * Estimates the number of cells that the specified height would take up.
+     */
+    public int estimateCellVSpan(int height) {
+        // The space for a page is the height - top padding (current page) - bottom padding (current
+        // page)
+        int availHeight = height - (getPaddingTop() + getPaddingBottom());
+
+        // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
+        int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
+
+        // We don't do anything fancy to determine if we squeeze another row in.
+        return n;
+    }
+
+    /** Returns an estimated center position of the cell at the specified index */
+    public int[] estimateCellPosition(int x, int y) {
+        return new int[] {
+                getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
+                getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
+        };
+    }
+
+    public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
+        mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
+        mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
+        requestLayout();
+    }
+
+    /**
+     * Estimates the width that the number of hSpan cells will take up.
+     */
+    public int estimateCellWidth(int hSpan) {
+        // TODO: we need to take widthGap into effect
+        return hSpan * mCellWidth;
+    }
+
+    /**
+     * Estimates the height that the number of vSpan cells will take up.
+     */
+    public int estimateCellHeight(int vSpan) {
+        // TODO: we need to take heightGap into effect
+        return vSpan * mCellHeight;
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof PagedViewCellLayout.LayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new PagedViewCellLayout.LayoutParams(p);
+    }
+
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        /**
+         * Horizontal location of the item in the grid.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellX;
+
+        /**
+         * Vertical location of the item in the grid.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellY;
+
+        /**
+         * Number of cells spanned horizontally by the item.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellHSpan;
+
+        /**
+         * Number of cells spanned vertically by the item.
+         */
+        @ViewDebug.ExportedProperty
+        public int cellVSpan;
+
+        /**
+         * Is this item currently being dragged
+         */
+        public boolean isDragging;
+
+        // a data object that you can bind to this layout params
+        private Object mTag;
+
+        // X coordinate of the view in the layout.
+        @ViewDebug.ExportedProperty
+        int x;
+        // Y coordinate of the view in the layout.
+        @ViewDebug.ExportedProperty
+        int y;
+
+        public LayoutParams() {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+            this.cellX = source.cellX;
+            this.cellY = source.cellY;
+            this.cellHSpan = source.cellHSpan;
+            this.cellVSpan = source.cellVSpan;
+        }
+
+        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            this.cellX = cellX;
+            this.cellY = cellY;
+            this.cellHSpan = cellHSpan;
+            this.cellVSpan = cellVSpan;
+        }
+
+        public void setup(Context context,
+                          int cellWidth, int cellHeight, int widthGap, int heightGap,
+                          int hStartPadding, int vStartPadding) {
+
+            final int myCellHSpan = cellHSpan;
+            final int myCellVSpan = cellVSpan;
+            final int myCellX = cellX;
+            final int myCellY = cellY;
+
+            width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+                    leftMargin - rightMargin;
+            height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+                    topMargin - bottomMargin;
+
+            if (LauncherAppState.getInstance().isScreenLarge()) {
+                x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
+                y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
+            } else {
+                x = myCellX * (cellWidth + widthGap) + leftMargin;
+                y = myCellY * (cellHeight + heightGap) + topMargin;
+            }
+        }
+
+        public Object getTag() {
+            return mTag;
+        }
+
+        public void setTag(Object tag) {
+            mTag = tag;
+        }
+
+        public String toString() {
+            return "(" + this.cellX + ", " + this.cellY + ", " +
+                this.cellHSpan + ", " + this.cellVSpan + ")";
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedViewCellLayoutChildren.java b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
new file mode 100644
index 0000000..84d2b1d
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
@@ -0,0 +1,161 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An abstraction of the original CellLayout which supports laying out items
+ * which span multiple cells into a grid-like layout.  Also supports dimming
+ * to give a preview of its contents.
+ */
+public class PagedViewCellLayoutChildren extends ViewGroup {
+    static final String TAG = "PagedViewCellLayout";
+
+    private boolean mCenterContent;
+
+    private int mCellWidth;
+    private int mCellHeight;
+    private int mWidthGap;
+    private int mHeightGap;
+
+    public PagedViewCellLayoutChildren(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
+    public void setGap(int widthGap, int heightGap) {
+        mWidthGap = widthGap;
+        mHeightGap = heightGap;
+        requestLayout();
+    }
+
+    public void setCellDimensions(int width, int height) {
+        mCellWidth = width;
+        mCellHeight = height;
+        requestLayout();
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (child != null) {
+            Rect r = new Rect();
+            child.getDrawingRect(r);
+            requestRectangleOnScreen(r);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+        }
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            PagedViewCellLayout.LayoutParams lp =
+                (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+            lp.setup(getContext(),
+                    mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+                    getPaddingLeft(),
+                    getPaddingTop());
+
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
+                    MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+                    MeasureSpec.EXACTLY);
+
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int count = getChildCount();
+
+        int offsetX = 0;
+        if (mCenterContent && count > 0) {
+            // determine the max width of all the rows and center accordingly
+            int maxRowX = 0;
+            int minRowX = Integer.MAX_VALUE;
+            for (int i = 0; i < count; i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != GONE) {
+                    PagedViewCellLayout.LayoutParams lp =
+                        (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+                    minRowX = Math.min(minRowX, lp.x);
+                    maxRowX = Math.max(maxRowX, lp.x + lp.width);
+                }
+            }
+            int maxRowWidth = maxRowX - minRowX;
+            offsetX = (getMeasuredWidth() - maxRowWidth) / 2;
+        }
+
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                PagedViewCellLayout.LayoutParams lp =
+                    (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+
+                int childLeft = offsetX + lp.x;
+                int childTop = lp.y;
+                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+            }
+        }
+    }
+
+    public void enableCenteredContent(boolean enabled) {
+        mCenterContent = enabled;
+    }
+
+    @Override
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = getChildAt(i);
+            view.setDrawingCacheEnabled(enabled);
+            // Update the drawing caches
+            if (!view.isHardwareAccelerated()) {
+                view.buildDrawingCache(true);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewGridLayout.java b/src/com/android/launcher3/PagedViewGridLayout.java
new file mode 100644
index 0000000..b286861
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewGridLayout.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.GridLayout;
+
+/**
+ * The grid based layout used strictly for the widget/wallpaper tab of the AppsCustomize pane
+ */
+public class PagedViewGridLayout extends GridLayout implements Page {
+    static final String TAG = "PagedViewGridLayout";
+
+    private int mCellCountX;
+    private int mCellCountY;
+    private Runnable mOnLayoutListener;
+
+    public PagedViewGridLayout(Context context, int cellCountX, int cellCountY) {
+        super(context, null, 0);
+        mCellCountX = cellCountX;
+        mCellCountY = cellCountY;
+    }
+
+    int getCellCountX() {
+        return mCellCountX;
+    }
+
+    int getCellCountY() {
+        return mCellCountY;
+    }
+
+    /**
+     * Clears all the key listeners for the individual widgets.
+     */
+    public void resetChildrenOnKeyListeners() {
+        int childCount = getChildCount();
+        for (int j = 0; j < childCount; ++j) {
+            getChildAt(j).setOnKeyListener(null);
+        }
+    }
+
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // PagedView currently has issues with different-sized pages since it calculates the
+        // offset of each page to scroll to before it updates the actual size of each page
+        // (which can change depending on the content if the contents aren't a fixed size).
+        // We work around this by having a minimum size on each widget page).
+        int widthSpecSize = Math.min(getSuggestedMinimumWidth(),
+                MeasureSpec.getSize(widthMeasureSpec));
+        int widthSpecMode = MeasureSpec.EXACTLY;
+        super.onMeasure(MeasureSpec.makeMeasureSpec(widthSpecSize, widthSpecMode),
+                heightMeasureSpec);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mOnLayoutListener = null;
+    }
+
+    public void setOnLayoutListener(Runnable r) {
+        mOnLayoutListener = r;
+    }
+
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mOnLayoutListener != null) {
+            mOnLayoutListener.run();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean result = super.onTouchEvent(event);
+        int count = getPageChildCount();
+        if (count > 0) {
+            // We only intercept the touch if we are tapping in empty space after the final row
+            View child = getChildOnPageAt(count - 1);
+            int bottom = child.getBottom();
+            result = result || (event.getY() < bottom);
+        }
+        return result;
+    }
+
+    @Override
+    public void removeAllViewsOnPage() {
+        removeAllViews();
+        mOnLayoutListener = null;
+        setLayerType(LAYER_TYPE_NONE, null);
+    }
+
+    @Override
+    public void removeViewOnPageAt(int index) {
+        removeViewAt(index);
+    }
+
+    @Override
+    public int getPageChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getChildOnPageAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public int indexOfChildOnPage(View v) {
+        return indexOfChild(v);
+    }
+
+    public static class LayoutParams extends FrameLayout.LayoutParams {
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
new file mode 100644
index 0000000..c6d5e49
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -0,0 +1,128 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
+ * drawables on the top).
+ */
+public class PagedViewIcon extends TextView {
+    /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */
+    public static interface PressedCallback {
+        void iconPressed(PagedViewIcon icon);
+    }
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "PagedViewIcon";
+    private static final float PRESS_ALPHA = 0.4f;
+
+    private PagedViewIcon.PressedCallback mPressedCallback;
+    private boolean mLockDrawableState = false;
+
+    private Bitmap mIcon;
+
+    public PagedViewIcon(Context context) {
+        this(context, null);
+    }
+
+    public PagedViewIcon(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Ensure we are using the right text size
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+    }
+
+    public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
+            PagedViewIcon.PressedCallback cb) {
+        mIcon = info.iconBitmap;
+        mPressedCallback = cb;
+        setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
+        setText(info.title);
+        setTag(info);
+    }
+
+    public void lockDrawableState() {
+        mLockDrawableState = true;
+    }
+
+    public void resetDrawableState() {
+        mLockDrawableState = false;
+        post(new Runnable() {
+            @Override
+            public void run() {
+                refreshDrawableState();
+            }
+        });
+    }
+
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        // We keep in the pressed state until resetDrawableState() is called to reset the press
+        // feedback
+        if (isPressed()) {
+            setAlpha(PRESS_ALPHA);
+            if (mPressedCallback != null) {
+                mPressedCallback.iconPressed(this);
+            }
+        } else if (!mLockDrawableState) {
+            setAlpha(1f);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // If text is transparent, don't draw any shadow
+        if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+            getPaint().clearShadowLayer();
+            super.draw(canvas);
+            return;
+        }
+
+        // We enhance the shadow by drawing the shadow twice
+        getPaint().setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f,
+                BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR);
+        super.draw(canvas);
+        canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+                getScrollX() + getWidth(),
+                getScrollY() + getHeight(), Region.Op.INTERSECT);
+        getPaint().setShadowLayer(BubbleTextView.SHADOW_SMALL_RADIUS, 0.0f, 0.0f,
+                BubbleTextView.SHADOW_SMALL_COLOUR);
+        super.draw(canvas);
+        canvas.restore();
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewIconCache.java b/src/com/android/launcher3/PagedViewIconCache.java
new file mode 100644
index 0000000..8d8924b
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewIconCache.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+
+/**
+ * Simple cache mechanism for PagedView outlines.
+ */
+public class PagedViewIconCache {
+    public static class Key {
+        public enum Type {
+            ApplicationInfoKey,
+            AppWidgetProviderInfoKey,
+            ResolveInfoKey
+        }
+        private final ComponentName mComponentName;
+        private final Type mType;
+
+        public Key(AppInfo info) {
+            mComponentName = info.componentName;
+            mType = Type.ApplicationInfoKey;
+        }
+        public Key(ResolveInfo info) {
+            final ComponentInfo ci = info.activityInfo != null ? info.activityInfo :
+                info.serviceInfo;
+            mComponentName = new ComponentName(ci.packageName, ci.name);
+            mType = Type.ResolveInfoKey;
+        }
+        public Key(AppWidgetProviderInfo info) {
+            mComponentName = info.provider;
+            mType = Type.AppWidgetProviderInfoKey;
+        }
+
+        private ComponentName getComponentName() {
+            return mComponentName;
+        }
+        public boolean isKeyType(Type t) {
+            return (mType == t);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof Key) {
+                Key k = (Key) o;
+                return mComponentName.equals(k.mComponentName);
+            }
+            return super.equals(o);
+        }
+        @Override
+        public int hashCode() {
+            return getComponentName().hashCode();
+        }
+    }
+
+    private final HashMap<Key, Bitmap> mIconOutlineCache = new HashMap<Key, Bitmap>();
+
+    public void clear() {
+        for (Key key : mIconOutlineCache.keySet()) {
+            mIconOutlineCache.get(key).recycle();
+        }
+        mIconOutlineCache.clear();
+    }
+    private void retainAll(HashSet<Key> keysToKeep, Key.Type t) {
+        HashSet<Key> keysToRemove = new HashSet<Key>(mIconOutlineCache.keySet());
+        keysToRemove.removeAll(keysToKeep);
+        for (Key key : keysToRemove) {
+            if (key.isKeyType(t)) {
+                mIconOutlineCache.get(key).recycle();
+                mIconOutlineCache.remove(key);
+            }
+        }
+    }
+    /** Removes all the keys to applications that aren't in the passed in collection */
+    public void retainAllApps(ArrayList<AppInfo> keys) {
+        HashSet<Key> keysSet = new HashSet<Key>();
+        for (AppInfo info : keys) {
+            keysSet.add(new Key(info));
+        }
+        retainAll(keysSet, Key.Type.ApplicationInfoKey);
+    }
+    /** Removes all the keys to shortcuts that aren't in the passed in collection */
+    public void retainAllShortcuts(List<ResolveInfo> keys) {
+        HashSet<Key> keysSet = new HashSet<Key>();
+        for (ResolveInfo info : keys) {
+            keysSet.add(new Key(info));
+        }
+        retainAll(keysSet, Key.Type.ResolveInfoKey);
+    }
+    /** Removes all the keys to widgets that aren't in the passed in collection */
+    public void retainAllAppWidgets(List<AppWidgetProviderInfo> keys) {
+        HashSet<Key> keysSet = new HashSet<Key>();
+        for (AppWidgetProviderInfo info : keys) {
+            keysSet.add(new Key(info));
+        }
+        retainAll(keysSet, Key.Type.AppWidgetProviderInfoKey);
+    }
+    public void addOutline(Key key, Bitmap b) {
+        mIconOutlineCache.put(key, b);
+    }
+    public void removeOutline(Key key) {
+        if (mIconOutlineCache.containsKey(key)) {
+            mIconOutlineCache.get(key).recycle();
+            mIconOutlineCache.remove(key);
+        }
+    }
+    public Bitmap getOutline(Key key) {
+        return mIconOutlineCache.get(key);
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java
new file mode 100644
index 0000000..45320a4
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWidget.java
@@ -0,0 +1,262 @@
+/*
+ * 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.launcher3;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * The linear layout used strictly for the widget/wallpaper tab of the customization tray
+ */
+public class PagedViewWidget extends LinearLayout {
+    static final String TAG = "PagedViewWidgetLayout";
+
+    private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
+    private static boolean sRecyclePreviewsWhenDetachedFromWindow = true;
+
+    private String mDimensionsFormatString;
+    CheckForShortPress mPendingCheckForShortPress = null;
+    ShortPressListener mShortPressListener = null;
+    boolean mShortPressTriggered = false;
+    static PagedViewWidget sShortpressTarget = null;
+    boolean mIsAppWidget;
+    private final Rect mOriginalImagePadding = new Rect();
+    private Object mInfo;
+    private WidgetPreviewLoader mWidgetPreviewLoader;
+
+    public PagedViewWidget(Context context) {
+        this(context, null);
+    }
+
+    public PagedViewWidget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources r = context.getResources();
+        mDimensionsFormatString = r.getString(R.string.widget_dims_format);
+
+        setWillNotDraw(false);
+        setClipToPadding(false);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+        mOriginalImagePadding.left = image.getPaddingLeft();
+        mOriginalImagePadding.top = image.getPaddingTop();
+        mOriginalImagePadding.right = image.getPaddingRight();
+        mOriginalImagePadding.bottom = image.getPaddingBottom();
+
+        // Ensure we are using the right text size
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        TextView name = (TextView) findViewById(R.id.widget_name);
+        if (name != null) {
+            name.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+        }
+        TextView dims = (TextView) findViewById(R.id.widget_dims);
+        if (dims != null) {
+            dims.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+        }
+    }
+
+    public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
+        sDeletePreviewsWhenDetachedFromWindow = value;
+    }
+
+    public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) {
+        sRecyclePreviewsWhenDetachedFromWindow = value;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (sDeletePreviewsWhenDetachedFromWindow) {
+            final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+            if (image != null) {
+                FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable();
+                if (sRecyclePreviewsWhenDetachedFromWindow &&
+                        mInfo != null && preview != null && preview.getBitmap() != null) {
+                    mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap());
+                }
+                image.setImageDrawable(null);
+            }
+        }
+    }
+
+    public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info,
+            int maxWidth, int[] cellSpan, WidgetPreviewLoader loader) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        mIsAppWidget = true;
+        mInfo = info;
+        final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+        if (maxWidth > -1) {
+            image.setMaxWidth(maxWidth);
+        }
+        final TextView name = (TextView) findViewById(R.id.widget_name);
+        name.setText(info.label);
+        final TextView dims = (TextView) findViewById(R.id.widget_dims);
+        if (dims != null) {
+            int hSpan = Math.min(cellSpan[0], (int) grid.numColumns);
+            int vSpan = Math.min(cellSpan[1], (int) grid.numRows);
+            dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
+        }
+        mWidgetPreviewLoader = loader;
+    }
+
+    public void applyFromResolveInfo(
+            PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
+        mIsAppWidget = false;
+        mInfo = info;
+        CharSequence label = info.loadLabel(pm);
+        final TextView name = (TextView) findViewById(R.id.widget_name);
+        name.setText(label);
+        final TextView dims = (TextView) findViewById(R.id.widget_dims);
+        if (dims != null) {
+            dims.setText(String.format(mDimensionsFormatString, 1, 1));
+        }
+        mWidgetPreviewLoader = loader;
+    }
+
+    public int[] getPreviewSize() {
+        final ImageView i = (ImageView) findViewById(R.id.widget_preview);
+        int[] maxSize = new int[2];
+        maxSize[0] = i.getWidth() - mOriginalImagePadding.left - mOriginalImagePadding.right;
+        maxSize[1] = i.getHeight() - mOriginalImagePadding.top;
+        return maxSize;
+    }
+
+    void applyPreview(FastBitmapDrawable preview, int index) {
+        final PagedViewWidgetImageView image =
+            (PagedViewWidgetImageView) findViewById(R.id.widget_preview);
+        if (preview != null) {
+            image.mAllowRequestLayout = false;
+            image.setImageDrawable(preview);
+            if (mIsAppWidget) {
+                // center horizontally
+                int[] imageSize = getPreviewSize();
+                int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
+                image.setPadding(mOriginalImagePadding.left + centerAmount,
+                        mOriginalImagePadding.top,
+                        mOriginalImagePadding.right,
+                        mOriginalImagePadding.bottom);
+            }
+            image.setAlpha(1f);
+            image.mAllowRequestLayout = true;
+        }
+    }
+
+    void setShortPressListener(ShortPressListener listener) {
+        mShortPressListener = listener;
+    }
+
+    interface ShortPressListener {
+        void onShortPress(View v);
+        void cleanUpShortPress(View v);
+    }
+
+    class CheckForShortPress implements Runnable {
+        public void run() {
+            if (sShortpressTarget != null) return;
+            if (mShortPressListener != null) {
+                mShortPressListener.onShortPress(PagedViewWidget.this);
+                sShortpressTarget = PagedViewWidget.this;
+            }
+            mShortPressTriggered = true;
+        }
+    }
+
+    private void checkForShortPress() {
+        if (sShortpressTarget != null) return;
+        if (mPendingCheckForShortPress == null) {
+            mPendingCheckForShortPress = new CheckForShortPress();
+        }
+        postDelayed(mPendingCheckForShortPress, 120);
+    }
+
+    /**
+     * Remove the longpress detection timer.
+     */
+    private void removeShortPressCallback() {
+        if (mPendingCheckForShortPress != null) {
+          removeCallbacks(mPendingCheckForShortPress);
+        }
+    }
+
+    private void cleanUpShortPress() {
+        removeShortPressCallback();
+        if (mShortPressTriggered) {
+            if (mShortPressListener != null) {
+                mShortPressListener.cleanUpShortPress(PagedViewWidget.this);
+            }
+            mShortPressTriggered = false;
+        }
+    }
+
+    static void resetShortPressTarget() {
+        sShortpressTarget = null;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+                cleanUpShortPress();
+                break;
+            case MotionEvent.ACTION_DOWN:
+                checkForShortPress();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                cleanUpShortPress();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                break;
+        }
+
+        // We eat up the touch events here, since the PagedView (which uses the same swiping
+        // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
+        // the user is scrolling between pages.  This means that if the pages themselves don't
+        // handle touch events, it gets forwarded up to PagedView itself, and it's own
+        // onTouchEvent() handling will prevent further intercept touch events from being called
+        // (it's the same view in that case).  This is not ideal, but to prevent more changes,
+        // we just always mark the touch event as handled.
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewWidgetImageView.java b/src/com/android/launcher3/PagedViewWidgetImageView.java
new file mode 100644
index 0000000..71f5eea
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWidgetImageView.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+class PagedViewWidgetImageView extends ImageView {
+    public boolean mAllowRequestLayout = true;
+
+    public PagedViewWidgetImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void requestLayout() {
+        if (mAllowRequestLayout) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.save();
+        canvas.clipRect(getScrollX() + getPaddingLeft(),
+                getScrollY() + getPaddingTop(),
+                getScrollX() + getRight() - getLeft() - getPaddingRight(),
+                getScrollY() + getBottom() - getTop() - getPaddingBottom());
+
+        super.onDraw(canvas);
+        canvas.restore();
+
+    }
+}
diff --git a/src/com/android/launcher3/PagedViewWithDraggableItems.java b/src/com/android/launcher3/PagedViewWithDraggableItems.java
new file mode 100644
index 0000000..2a29c33
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWithDraggableItems.java
@@ -0,0 +1,170 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+
+/* Class that does most of the work of enabling dragging items out of a PagedView by performing a
+ * vertical drag. Used by both CustomizePagedView and AllAppsPagedView.
+ * Subclasses must do the following:
+ *   * call setDragSlopeThreshold after making an instance of the PagedViewWithDraggableItems
+ *   * call child.setOnLongClickListener(this) and child.setOnTouchListener(this) on all children
+ *       (good place to do it is in syncPageItems)
+ *   * override beginDragging(View) (but be careful to call super.beginDragging(View)
+ *
+ */
+public abstract class PagedViewWithDraggableItems extends PagedView
+    implements View.OnLongClickListener, View.OnTouchListener {
+    private View mLastTouchedItem;
+    private boolean mIsDragging;
+    private boolean mIsDragEnabled;
+    private float mDragSlopeThreshold;
+    private Launcher mLauncher;
+
+    public PagedViewWithDraggableItems(Context context) {
+        this(context, null);
+    }
+
+    public PagedViewWithDraggableItems(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLauncher = (Launcher) context;
+    }
+
+    protected boolean beginDragging(View v) {
+        boolean wasDragging = mIsDragging;
+        mIsDragging = true;
+        return !wasDragging;
+    }
+
+    protected void cancelDragging() {
+        mIsDragging = false;
+        mLastTouchedItem = null;
+        mIsDragEnabled = false;
+    }
+
+    private void handleTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                cancelDragging();
+                mIsDragEnabled = true;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mTouchState != TOUCH_STATE_SCROLLING && !mIsDragging && mIsDragEnabled) {
+                    determineDraggingStart(ev);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        handleTouchEvent(ev);
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        handleTouchEvent(ev);
+        return super.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        mLastTouchedItem = v;
+        mIsDragEnabled = true;
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        // Return early if this is not initiated from a touch
+        if (!v.isInTouchMode()) return false;
+        // Return early if we are still animating the pages
+        if (mNextPage != INVALID_PAGE) return false;
+        // When we have exited all apps or are in transition, disregard long clicks
+        if (!mLauncher.isAllAppsVisible() ||
+                mLauncher.getWorkspace().isSwitchingState()) return false;
+        // Return if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return false;
+
+        return beginDragging(v);
+    }
+
+    /*
+     * Determines if we should change the touch state to start scrolling after the
+     * user moves their touch point too far.
+     */
+    protected void determineScrollingStart(MotionEvent ev) {
+        if (!mIsDragging) super.determineScrollingStart(ev);
+    }
+
+    /*
+     * Determines if we should change the touch state to start dragging after the
+     * user moves their touch point far enough.
+     */
+    protected void determineDraggingStart(MotionEvent ev) {
+        /*
+         * Locally do absolute value. mLastMotionX is set to the y value
+         * of the down event.
+         */
+        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        final float x = ev.getX(pointerIndex);
+        final float y = ev.getY(pointerIndex);
+        final int xDiff = (int) Math.abs(x - mLastMotionX);
+        final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+        final int touchSlop = mTouchSlop;
+        boolean yMoved = yDiff > touchSlop;
+        boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold;
+
+        if (isUpwardMotion && yMoved && mLastTouchedItem != null) {
+            // Drag if the user moved far enough along the Y axis
+            beginDragging(mLastTouchedItem);
+
+            // Cancel any pending long press
+            if (mAllowLongPress) {
+                mAllowLongPress = false;
+                // Try canceling the long press. It could also have been scheduled
+                // by a distant descendant, so use the mAllowLongPress flag to block
+                // everything
+                final View currentPage = getPageAt(mCurrentPage);
+                if (currentPage != null) {
+                    currentPage.cancelLongPress();
+                }
+            }
+        }
+    }
+
+    public void setDragSlopeThreshold(float dragSlopeThreshold) {
+        mDragSlopeThreshold = dragSlopeThreshold;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        cancelDragging();
+        super.onDetachedFromWindow();
+    }
+}
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
new file mode 100644
index 0000000..967cc92
--- /dev/null
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.launcher3;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+/**
+ * We pass this object with a drag from the customization tray
+ */
+class PendingAddItemInfo extends ItemInfo {
+    /**
+     * The component that will be created.
+     */
+    ComponentName componentName;
+}
+
+class PendingAddShortcutInfo extends PendingAddItemInfo {
+
+    ActivityInfo shortcutActivityInfo;
+
+    public PendingAddShortcutInfo(ActivityInfo activityInfo) {
+        shortcutActivityInfo = activityInfo;
+    }
+
+    @Override
+    public String toString() {
+        return "Shortcut: " + shortcutActivityInfo.packageName;
+    }
+}
+
+class PendingAddWidgetInfo extends PendingAddItemInfo {
+    int minWidth;
+    int minHeight;
+    int minResizeWidth;
+    int minResizeHeight;
+    int previewImage;
+    int icon;
+    AppWidgetProviderInfo info;
+    AppWidgetHostView boundWidget;
+    Bundle bindOptions = null;
+
+    // Any configuration data that we want to pass to a configuration activity when
+    // starting up a widget
+    String mimeType;
+    Parcelable configurationData;
+
+    public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+        this.info = i;
+        componentName = i.provider;
+        minWidth = i.minWidth;
+        minHeight = i.minHeight;
+        minResizeWidth = i.minResizeWidth;
+        minResizeHeight = i.minResizeHeight;
+        previewImage = i.previewImage;
+        icon = i.icon;
+        if (dataMimeType != null && data != null) {
+            mimeType = dataMimeType;
+            configurationData = data;
+        }
+    }
+
+    // Copy constructor
+    public PendingAddWidgetInfo(PendingAddWidgetInfo copy) {
+        minWidth = copy.minWidth;
+        minHeight = copy.minHeight;
+        minResizeWidth = copy.minResizeWidth;
+        minResizeHeight = copy.minResizeHeight;
+        previewImage = copy.previewImage;
+        icon = copy.icon;
+        info = copy.info;
+        boundWidget = copy.boundWidget;
+        mimeType = copy.mimeType;
+        configurationData = copy.configurationData;
+        componentName = copy.componentName;
+        itemType = copy.itemType;
+        spanX = copy.spanX;
+        spanY = copy.spanY;
+        minSpanX = copy.minSpanX;
+        minSpanY = copy.minSpanY;
+        bindOptions = copy.bindOptions == null ? null : (Bundle) copy.bindOptions.clone();
+    }
+
+    @Override
+    public String toString() {
+        return "Widget: " + componentName.toShortString();
+    }
+}
diff --git a/src/com/android/launcher3/PreloadReceiver.java b/src/com/android/launcher3/PreloadReceiver.java
new file mode 100644
index 0000000..75e5c98
--- /dev/null
+++ b/src/com/android/launcher3/PreloadReceiver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class PreloadReceiver extends BroadcastReceiver {
+    private static final String TAG = "Launcher.PreloadReceiver";
+    private static final boolean LOGD = false;
+
+    public static final String EXTRA_WORKSPACE_NAME =
+            "com.android.launcher3.action.EXTRA_WORKSPACE_NAME";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final LauncherProvider provider = LauncherAppState.getLauncherProvider();
+        if (provider != null) {
+            String name = intent.getStringExtra(EXTRA_WORKSPACE_NAME);
+            final int workspaceResId = !TextUtils.isEmpty(name)
+                    ? context.getResources().getIdentifier(name, "xml", "com.android.launcher3") : 0;
+            if (LOGD) {
+                Log.d(TAG, "workspace name: " + name + " id: " + workspaceResId);
+            }
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    provider.loadDefaultFavoritesIfNecessary(workspaceResId);
+                }
+            }).start();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/RampUpScroller.java b/src/com/android/launcher3/RampUpScroller.java
new file mode 100644
index 0000000..89eb579
--- /dev/null
+++ b/src/com/android/launcher3/RampUpScroller.java
@@ -0,0 +1,120 @@
+/*
+ * 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.launcher3;
+
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * Scroller that gradually reaches a target velocity.
+ */
+class RampUpScroller {
+    private final Interpolator mInterpolator;
+    private final long mRampUpTime;
+
+    private long mStartTime;
+    private long mDeltaTime;
+    private float mTargetVelocityX;
+    private float mTargetVelocityY;
+    private int mDeltaX;
+    private int mDeltaY;
+
+    /**
+     * Creates a new ramp-up scroller that reaches full velocity after a
+     * specified duration.
+     *
+     * @param rampUpTime Duration before the scroller reaches target velocity.
+     */
+    public RampUpScroller(long rampUpTime) {
+        mInterpolator = new AccelerateInterpolator();
+        mRampUpTime = rampUpTime;
+    }
+
+    /**
+     * Starts the scroller at the current animation time.
+     */
+    public void start() {
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mDeltaTime = mStartTime;
+    }
+
+    /**
+     * Computes the current scroll deltas. This usually only be called after
+     * starting the scroller with {@link #start()}.
+     *
+     * @see #getDeltaX()
+     * @see #getDeltaY()
+     */
+    public void computeScrollDelta() {
+        final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+        final long elapsedSinceStart = currentTime - mStartTime;
+        final float scale;
+        if (elapsedSinceStart < mRampUpTime) {
+            scale = mInterpolator.getInterpolation((float) elapsedSinceStart / mRampUpTime);
+        } else {
+            scale = 1f;
+        }
+
+        final long elapsedSinceDelta = currentTime - mDeltaTime;
+        mDeltaTime = currentTime;
+
+        mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
+        mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
+    }
+
+    /**
+     * Sets the target velocity for this scroller.
+     *
+     * @param x The target X velocity in pixels per millisecond.
+     * @param y The target Y velocity in pixels per millisecond.
+     */
+    public void setTargetVelocity(float x, float y) {
+        mTargetVelocityX = x;
+        mTargetVelocityY = y;
+    }
+
+    /**
+     * @return The target X velocity for this scroller.
+     */
+    public float getTargetVelocityX() {
+        return mTargetVelocityX;
+    }
+
+    /**
+     * @return The target Y velocity for this scroller.
+     */
+    public float getTargetVelocityY() {
+        return mTargetVelocityY;
+    }
+
+    /**
+     * The distance traveled in the X-coordinate computed by the last call to
+     * {@link #computeScrollDelta()}.
+     */
+    public int getDeltaX() {
+        return mDeltaX;
+    }
+
+    /**
+     * The distance traveled in the Y-coordinate computed by the last call to
+     * {@link #computeScrollDelta()}.
+     */
+    public int getDeltaY() {
+        return mDeltaY;
+    }
+}
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
new file mode 100644
index 0000000..8d5b005
--- /dev/null
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -0,0 +1,244 @@
+/*
+ * 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.launcher3;
+
+import android.app.Activity;
+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 android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
+    private static String TAG = "Launcher3.SavedWallpaperImages";
+    private ImageDb mDb;
+    ArrayList<SavedWallpaperTile> mImages;
+    Context mContext;
+    LayoutInflater mLayoutInflater;
+
+    public static class SavedWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private int mDbId;
+        private Drawable mThumb;
+        public SavedWallpaperTile(int dbId, Drawable thumb) {
+            mDbId = dbId;
+            mThumb = thumb;
+        }
+        @Override
+        public void onClick(WallpaperPickerActivity a) {
+            String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+            File file = new File(a.getFilesDir(), imageFilename);
+            CropView v = a.getCropView();
+            int rotation = WallpaperCropActivity.getRotationFromExif(file.getAbsolutePath());
+            v.setTileSource(
+                    new BitmapRegionTileSource(a, file.getAbsolutePath(), 1024, rotation), null);
+            v.moveToLeft();
+            v.setTouchEnabled(false);
+        }
+        @Override
+        public void onSave(WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+            a.setWallpaper(imageFilename, finishActivityWhenDone);
+        }
+        @Override
+        public void onDelete(WallpaperPickerActivity a) {
+            a.getSavedImages().deleteImage(mDbId);
+        }
+        @Override
+        public boolean isSelectable() {
+            return true;
+        }
+        @Override
+        public boolean isNamelessWallpaper() {
+            return true;
+        }
+    }
+
+    public SavedWallpaperImages(Activity context) {
+        mDb = new ImageDb(context);
+        mContext = context;
+        mLayoutInflater = context.getLayoutInflater();
+    }
+
+    public void loadThumbnailsAndImageIdList() {
+        mImages = new ArrayList<SavedWallpaperTile>();
+        SQLiteDatabase db = mDb.getReadableDatabase();
+        Cursor result = db.query(ImageDb.TABLE_NAME,
+                new String[] { ImageDb.COLUMN_ID,
+                    ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME }, // cols to return
+                null, // select query
+                null, // args to select query
+                null,
+                null,
+                ImageDb.COLUMN_ID + " DESC",
+                null);
+
+        while (result.moveToNext()) {
+            String filename = result.getString(1);
+            File file = new File(mContext.getFilesDir(), filename);
+
+            Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
+            if (thumb != null) {
+                mImages.add(new SavedWallpaperTile(result.getInt(0), new BitmapDrawable(thumb)));
+            }
+        }
+        result.close();
+    }
+
+    public int getCount() {
+        return mImages.size();
+    }
+
+    public SavedWallpaperTile getItem(int position) {
+        return mImages.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        Drawable thumbDrawable = mImages.get(position).mThumb;
+        if (thumbDrawable == null) {
+            Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+        }
+        return WallpaperPickerActivity.createImageTileView(
+                mLayoutInflater, position, convertView, parent, thumbDrawable);
+    }
+
+    public String getImageFilename(int id) {
+        Pair<String, String> filenames = getImageFilenames(id);
+        if (filenames != null) {
+            return filenames.second;
+        }
+        return null;
+    }
+
+    private Pair<String, String> getImageFilenames(int id) {
+        SQLiteDatabase db = mDb.getReadableDatabase();
+        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.getCount() > 0) {
+            result.moveToFirst();
+            String thumbFilename = result.getString(0);
+            String imageFilename = result.getString(1);
+            result.close();
+            return new Pair<String, String>(thumbFilename, imageFilename);
+        } else {
+            return null;
+        }
+    }
+
+    public void deleteImage(int id) {
+        Pair<String, String> filenames = getImageFilenames(id);
+        File imageFile = new File(mContext.getFilesDir(), filenames.first);
+        imageFile.delete();
+        File thumbFile = new File(mContext.getFilesDir(), filenames.second);
+        thumbFile.delete();
+        SQLiteDatabase db = mDb.getWritableDatabase();
+        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);
+        }
+    }
+
+    static class ImageDb extends SQLiteOpenHelper {
+        final static int DB_VERSION = 1;
+        final static String DB_NAME = "saved_wallpaper_images.db";
+        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";
+
+        Context mContext;
+
+        public ImageDb(Context context) {
+            super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
+            // Store the context for later use
+            mContext = context;
+        }
+
+        @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/launcher3/ScrimView.java b/src/com/android/launcher3/ScrimView.java
new file mode 100644
index 0000000..6831fe3
--- /dev/null
+++ b/src/com/android/launcher3/ScrimView.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class ScrimView extends FrameLayout implements Insettable {
+
+    public ScrimView(Context context) {
+        this(context, null, 0);
+    }
+
+    public ScrimView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ScrimView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Do nothing
+    }
+}
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
new file mode 100644
index 0000000..e681aa1
--- /dev/null
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+
+/*
+ * Ths bar will manage the transition between the QSB search bar and the delete drop
+ * targets so that each of the individual IconDropTargets don't have to.
+ */
+public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
+
+    private static final int sTransitionInDuration = 200;
+    private static final int sTransitionOutDuration = 175;
+
+    private ObjectAnimator mDropTargetBarAnim;
+    private ObjectAnimator mQSBSearchBarAnim;
+    private static final AccelerateInterpolator sAccelerateInterpolator =
+            new AccelerateInterpolator();
+
+    private boolean mIsSearchBarHidden;
+    private View mQSBSearchBar;
+    private View mDropTargetBar;
+    private ButtonDropTarget mInfoDropTarget;
+    private ButtonDropTarget mDeleteDropTarget;
+    private int mBarHeight;
+    private boolean mDeferOnDragEnd = false;
+
+    private Drawable mPreviousBackground;
+    private boolean mEnableDropDownDropTargets;
+
+    public SearchDropTargetBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setup(Launcher launcher, DragController dragController) {
+        dragController.addDragListener(this);
+        dragController.addDragListener(mInfoDropTarget);
+        dragController.addDragListener(mDeleteDropTarget);
+        dragController.addDropTarget(mInfoDropTarget);
+        dragController.addDropTarget(mDeleteDropTarget);
+        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+        mInfoDropTarget.setLauncher(launcher);
+        mDeleteDropTarget.setLauncher(launcher);
+        mQSBSearchBar = launcher.getQsbBar();
+        if (mEnableDropDownDropTargets) {
+            mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "translationY", 0,
+                    -mBarHeight);
+        } else {
+            mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
+        }
+        setupAnimation(mQSBSearchBarAnim, mQSBSearchBar);
+    }
+
+    private void prepareStartAnimation(View v) {
+        // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
+        // callback below)
+        v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+    }
+
+    private void setupAnimation(ObjectAnimator anim, final View v) {
+        anim.setInterpolator(sAccelerateInterpolator);
+        anim.setDuration(sTransitionInDuration);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
+        });
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Get the individual components
+        mDropTargetBar = findViewById(R.id.drag_target_bar);
+        mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
+        mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text);
+
+        mInfoDropTarget.setSearchDropTargetBar(this);
+        mDeleteDropTarget.setSearchDropTargetBar(this);
+
+        mEnableDropDownDropTargets =
+            getResources().getBoolean(R.bool.config_useDropTargetDownTransition);
+
+        // Create the various fade animations
+        if (mEnableDropDownDropTargets) {
+            LauncherAppState app = LauncherAppState.getInstance();
+            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+            mBarHeight = grid.searchBarSpaceHeightPx;
+            mDropTargetBar.setTranslationY(-mBarHeight);
+            mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "translationY",
+                    -mBarHeight, 0f);
+
+        } else {
+            mDropTargetBar.setAlpha(0f);
+            mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
+        }
+        setupAnimation(mDropTargetBarAnim, mDropTargetBar);
+    }
+
+    public void finishAnimations() {
+        prepareStartAnimation(mDropTargetBar);
+        mDropTargetBarAnim.reverse();
+        prepareStartAnimation(mQSBSearchBar);
+        mQSBSearchBarAnim.reverse();
+    }
+
+    /*
+     * Shows and hides the search bar.
+     */
+    public void showSearchBar(boolean animated) {
+        if (!mIsSearchBarHidden) return;
+        if (animated) {
+            prepareStartAnimation(mQSBSearchBar);
+            mQSBSearchBarAnim.reverse();
+        } else {
+            mQSBSearchBarAnim.cancel();
+            if (mEnableDropDownDropTargets) {
+                mQSBSearchBar.setTranslationY(0);
+            } else {
+                mQSBSearchBar.setAlpha(1f);
+            }
+        }
+        mIsSearchBarHidden = false;
+    }
+    public void hideSearchBar(boolean animated) {
+        if (mIsSearchBarHidden) return;
+        if (animated) {
+            prepareStartAnimation(mQSBSearchBar);
+            mQSBSearchBarAnim.start();
+        } else {
+            mQSBSearchBarAnim.cancel();
+            if (mEnableDropDownDropTargets) {
+                mQSBSearchBar.setTranslationY(-mBarHeight);
+            } else {
+                mQSBSearchBar.setAlpha(0f);
+            }
+        }
+        mIsSearchBarHidden = true;
+    }
+
+    /*
+     * Gets various transition durations.
+     */
+    public int getTransitionInDuration() {
+        return sTransitionInDuration;
+    }
+    public int getTransitionOutDuration() {
+        return sTransitionOutDuration;
+    }
+
+    /*
+     * DragController.DragListener implementation
+     */
+    @Override
+    public void onDragStart(DragSource source, Object info, int dragAction) {
+        // Animate out the QSB search bar, and animate in the drop target bar
+        prepareStartAnimation(mDropTargetBar);
+        mDropTargetBarAnim.start();
+        if (!mIsSearchBarHidden) {
+            prepareStartAnimation(mQSBSearchBar);
+            mQSBSearchBarAnim.start();
+        }
+    }
+
+    public void deferOnDragEnd() {
+        mDeferOnDragEnd = true;
+    }
+
+    @Override
+    public void onDragEnd() {
+        if (!mDeferOnDragEnd) {
+            // Restore the QSB search bar, and animate out the drop target bar
+            prepareStartAnimation(mDropTargetBar);
+            mDropTargetBarAnim.reverse();
+            if (!mIsSearchBarHidden) {
+                prepareStartAnimation(mQSBSearchBar);
+                mQSBSearchBarAnim.reverse();
+            }
+        } else {
+            mDeferOnDragEnd = false;
+        }
+    }
+
+    public void onSearchPackagesChanged(boolean searchVisible, boolean voiceVisible) {
+        if (mQSBSearchBar != null) {
+            Drawable bg = mQSBSearchBar.getBackground();
+            if (bg != null && (!searchVisible && !voiceVisible)) {
+                // Save the background and disable it
+                mPreviousBackground = bg;
+                mQSBSearchBar.setBackgroundResource(0);
+            } else if (mPreviousBackground != null && (searchVisible || voiceVisible)) {
+                // Restore the background
+                mQSBSearchBar.setBackground(mPreviousBackground);
+            }
+        }
+    }
+
+    public Rect getSearchBarBounds() {
+        if (mQSBSearchBar != null) {
+            final int[] pos = new int[2];
+            mQSBSearchBar.getLocationOnScreen(pos);
+
+            final Rect rect = new Rect();
+            rect.left = pos[0];
+            rect.top = pos[1];
+            rect.right = pos[0] + mQSBSearchBar.getWidth();
+            rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+            return rect;
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
new file mode 100644
index 0000000..bb5601e
--- /dev/null
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class ShortcutAndWidgetContainer extends ViewGroup {
+    static final String TAG = "CellLayoutChildren";
+
+    // These are temporary variables to prevent having to allocate a new object just to
+    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+    private final int[] mTmpCellXY = new int[2];
+
+    private final WallpaperManager mWallpaperManager;
+
+    private boolean mIsHotseatLayout;
+
+    private int mCellWidth;
+    private int mCellHeight;
+
+    private int mWidthGap;
+    private int mHeightGap;
+
+    private int mCountX;
+    private int mCountY;
+
+    private boolean mInvertIfRtl = false;
+
+    public ShortcutAndWidgetContainer(Context context) {
+        super(context);
+        mWallpaperManager = WallpaperManager.getInstance(context);
+    }
+
+    public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap,
+            int countX, int countY) {
+        mCellWidth = cellWidth;
+        mCellHeight = cellHeight;
+        mWidthGap = widthGap;
+        mHeightGap = heightGap;
+        mCountX = countX;
+        mCountY = countY;
+    }
+
+    public View getChildAt(int x, int y) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
+                    (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        @SuppressWarnings("all") // suppress dead code warning
+        final boolean debug = false;
+        if (debug) {
+            // Debug drawing for hit space
+            Paint p = new Paint();
+            p.setColor(0x6600FF00);
+            for (int i = getChildCount() - 1; i >= 0; i--) {
+                final View child = getChildAt(i);
+                final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+                canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p);
+            }
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int count = getChildCount();
+
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                measureChild(child);
+            }
+        }
+    }
+
+    public void setupLp(CellLayout.LayoutParams lp) {
+        lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
+                mCountX);
+    }
+
+    // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
+    public void setInvertIfRtl(boolean invert) {
+        mInvertIfRtl = invert;
+    }
+
+    public void setIsHotseat(boolean isHotseat) {
+        mIsHotseatLayout = isHotseat;
+    }
+
+    int getCellContentWidth() {
+        final LauncherAppState app = LauncherAppState.getInstance();
+        final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
+                grid.hotseatCellWidthPx: grid.cellWidthPx);
+    }
+
+    int getCellContentHeight() {
+        final LauncherAppState app = LauncherAppState.getInstance();
+        final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
+                grid.hotseatCellHeightPx : grid.cellHeightPx);
+    }
+
+    public void measureChild(View child) {
+        final LauncherAppState app = LauncherAppState.getInstance();
+        final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        final int cellWidth = mCellWidth;
+        final int cellHeight = mCellHeight;
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        if (!lp.isFullscreen) {
+            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
+                    mCountX);
+
+            if (child instanceof LauncherAppWidgetHostView) {
+                // Widgets have their own padding, so skip
+            } else {
+                // Otherwise, center the icon
+                int cHeight = getCellContentHeight();
+                int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
+                int cellPaddingX = (int) (grid.edgeMarginPx / 2f);
+                child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
+            }
+        } else {
+            lp.x = 0;
+            lp.y = 0;
+            lp.width = getMeasuredWidth();
+            lp.height = getMeasuredHeight();
+        }
+        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+        int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+                MeasureSpec.EXACTLY);
+        child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+    }
+
+    private boolean invertLayoutHorizontally() {
+        return mInvertIfRtl && isLayoutRtl();
+    }
+
+    public boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+                int childLeft = lp.x;
+                int childTop = lp.y;
+                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+                if (lp.dropped) {
+                    lp.dropped = false;
+
+                    final int[] cellXY = mTmpCellXY;
+                    getLocationOnScreen(cellXY);
+                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+                            WallpaperManager.COMMAND_DROP,
+                            cellXY[0] + childLeft + lp.width / 2,
+                            cellXY[1] + childTop + lp.height / 2, 0, null);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (child != null) {
+            Rect r = new Rect();
+            child.getDrawingRect(r);
+            requestRectangleOnScreen(r);
+        }
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
+    @Override
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = getChildAt(i);
+            view.setDrawingCacheEnabled(enabled);
+            // Update the drawing caches
+            if (!view.isHardwareAccelerated() && enabled) {
+                view.buildDrawingCache(true);
+            }
+        }
+    }
+
+    @Override
+    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+        super.setChildrenDrawnWithCacheEnabled(enabled);
+    }
+}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
new file mode 100644
index 0000000..dafabb8
--- /dev/null
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a launchable icon on the workspaces and in folders.
+ */
+class ShortcutInfo extends ItemInfo {
+
+    /**
+     * The intent used to start the application.
+     */
+    Intent intent;
+
+    /**
+     * Indicates whether the icon comes from an application's resource (if false)
+     * or from a custom Bitmap (if true.)
+     */
+    boolean customIcon;
+
+    /**
+     * Indicates whether we're using the default fallback icon instead of something from the
+     * app.
+     */
+    boolean usingFallbackIcon;
+
+    /**
+     * If isShortcut=true and customIcon=false, this contains a reference to the
+     * shortcut icon as an application's resource.
+     */
+    Intent.ShortcutIconResource iconResource;
+
+    /**
+     * The application icon.
+     */
+    private Bitmap mIcon;
+
+    long firstInstallTime;
+    int flags = 0;
+
+    ShortcutInfo() {
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+    }
+
+    protected Intent getIntent() {
+        return intent;
+    }
+    
+    public ShortcutInfo(Context context, ShortcutInfo info) {
+        super(info);
+        title = info.title.toString();
+        intent = new Intent(info.intent);
+        if (info.iconResource != null) {
+            iconResource = new Intent.ShortcutIconResource();
+            iconResource.packageName = info.iconResource.packageName;
+            iconResource.resourceName = info.iconResource.resourceName;
+        }
+        mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
+        customIcon = info.customIcon;
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
+    }
+
+    /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
+    public ShortcutInfo(AppInfo info) {
+        super(info);
+        title = info.title.toString();
+        intent = new Intent(info.intent);
+        customIcon = false;
+        flags = info.flags;
+        firstInstallTime = info.firstInstallTime;
+    }
+
+    public static PackageInfo getPackageInfo(Context context, String packageName) {
+        PackageInfo pi = null;
+        try {
+            PackageManager pm = context.getPackageManager();
+            pi = pm.getPackageInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            Log.d("ShortcutInfo", "PackageManager.getPackageInfo failed for " + packageName);
+        }
+        return pi;
+    }
+
+    void initFlagsAndFirstInstallTime(PackageInfo pi) {
+        flags = AppInfo.initFlags(pi);
+        firstInstallTime = AppInfo.initFirstInstallTime(pi);
+    }
+
+    public void setIcon(Bitmap b) {
+        mIcon = b;
+    }
+
+    public Bitmap getIcon(IconCache iconCache) {
+        if (mIcon == null) {
+            updateIcon(iconCache);
+        }
+        return mIcon;
+    }
+
+    public void updateIcon(IconCache iconCache) {
+        mIcon = iconCache.getIcon(intent);
+        usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
+    }
+
+    /**
+     * Creates the application intent based on a component name and various launch flags.
+     * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
+     *
+     * @param className the class name of the component representing the intent
+     * @param launchFlags the launch flags
+     */
+    final void setActivity(Context context, ComponentName className, int launchFlags) {
+        intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setComponent(className);
+        intent.setFlags(launchFlags);
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
+    }
+
+    @Override
+    void onAddToDatabase(ContentValues values) {
+        super.onAddToDatabase(values);
+
+        String titleStr = title != null ? title.toString() : null;
+        values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
+
+        String uri = intent != null ? intent.toUri(0) : null;
+        values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
+
+        if (customIcon) {
+            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
+            writeBitmap(values, mIcon);
+        } else {
+            if (!usingFallbackIcon) {
+                writeBitmap(values, mIcon);
+            }
+            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
+            if (iconResource != null) {
+                values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
+                        iconResource.packageName);
+                values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                        iconResource.resourceName);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id
+                + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
+                + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
+                + " dropPos=" + dropPos + ")";
+    }
+
+    public static void dumpShortcutInfoList(String tag, String label,
+            ArrayList<ShortcutInfo> list) {
+        Log.d(tag, label + " size=" + list.size());
+        for (ShortcutInfo info: list) {
+            Log.d(tag, "   title=\"" + info.title + " icon=" + info.mIcon
+                    + " customIcon=" + info.customIcon);
+        }
+    }
+}
+
diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java
new file mode 100644
index 0000000..a45dbbf
--- /dev/null
+++ b/src/com/android/launcher3/SmoothPagedView.java
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+public abstract class SmoothPagedView extends PagedView {
+    private static final float SMOOTHING_SPEED = 0.75f;
+    private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
+
+    private float mBaseLineFlingVelocity;
+    private float mFlingVelocityInfluence;
+
+    static final int DEFAULT_MODE = 0;
+    static final int X_LARGE_MODE = 1;
+
+    int mScrollMode;
+
+    private Interpolator mScrollInterpolator;
+
+    public static class OvershootInterpolator implements Interpolator {
+        private static final float DEFAULT_TENSION = 1.3f;
+        private float mTension;
+
+        public OvershootInterpolator() {
+            mTension = DEFAULT_TENSION;
+        }
+
+        public void setDistance(int distance) {
+            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
+        }
+
+        public void disableSettle() {
+            mTension = 0.f;
+        }
+
+        public float getInterpolation(float t) {
+            // _o(t) = t * t * ((tension + 1) * t + tension)
+            // o(t) = _o(t - 1) + 1
+            t -= 1.0f;
+            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+        }
+    }
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attributes set containing the Workspace's customization values.
+     */
+    public SmoothPagedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attributes set containing the Workspace's customization values.
+     * @param defStyle Unused.
+     */
+    public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mUsePagingTouchSlop = false;
+
+        // This means that we'll take care of updating the scroll parameter ourselves (we do it
+        // in computeScroll), we only do this in the OVERSHOOT_MODE, ie. on phones
+        mDeferScrollUpdate = mScrollMode != X_LARGE_MODE;
+    }
+
+    protected int getScrollMode() {
+        return X_LARGE_MODE;
+    }
+
+    /**
+     * Initializes various states for this workspace.
+     */
+    @Override
+    protected void init() {
+        super.init();
+
+        mScrollMode = getScrollMode();
+        if (mScrollMode == DEFAULT_MODE) {
+            mBaseLineFlingVelocity = 2500.0f;
+            mFlingVelocityInfluence = 0.4f;
+            mScrollInterpolator = new OvershootInterpolator();
+            mScroller = new Scroller(getContext(), mScrollInterpolator);
+        }
+    }
+
+    @Override
+    protected void snapToDestination() {
+        if (mScrollMode == X_LARGE_MODE) {
+            super.snapToDestination();
+        } else {
+            snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
+        }
+    }
+
+    @Override
+    protected void snapToPageWithVelocity(int whichPage, int velocity) {
+        if (mScrollMode == X_LARGE_MODE) {
+            super.snapToPageWithVelocity(whichPage, velocity);
+        } else {
+            snapToPageWithVelocity(whichPage, 0, true);
+        }
+    }
+
+    private void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) {
+            // if (!mScroller.isFinished()) return;
+
+        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+
+        final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
+        final int newX = getScrollForPage(whichPage);
+        final int delta = newX - mUnboundedScrollX;
+        int duration = (screenDelta + 1) * 100;
+
+        if (!mScroller.isFinished()) {
+            mScroller.abortAnimation();
+        }
+
+        if (settle) {
+            ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+        } else {
+            ((OvershootInterpolator) mScrollInterpolator).disableSettle();
+        }
+
+        velocity = Math.abs(velocity);
+        if (velocity > 0) {
+            duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
+        } else {
+            duration += 100;
+        }
+
+        snapToPage(whichPage, delta, duration);
+    }
+
+    @Override
+    protected void snapToPage(int whichPage) {
+       if (mScrollMode == X_LARGE_MODE) {
+           super.snapToPage(whichPage);
+       } else {
+           snapToPageWithVelocity(whichPage, 0, false);
+       }
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScrollMode == X_LARGE_MODE) {
+            super.computeScroll();
+        } else {
+            boolean scrollComputed = computeScrollHelper();
+
+            if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
+                final float now = System.nanoTime() / NANOTIME_DIV;
+                final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
+
+                final float dx = mTouchX - mUnboundedScrollX;
+                scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY());
+                mSmoothingTime = now;
+
+                // Keep generating points as long as we're more than 1px away from the target
+                if (dx > 1.f || dx < -1.f) {
+                    invalidate();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/SpringLoadedDragController.java b/src/com/android/launcher3/SpringLoadedDragController.java
new file mode 100644
index 0000000..45edaef
--- /dev/null
+++ b/src/com/android/launcher3/SpringLoadedDragController.java
@@ -0,0 +1,62 @@
+/*
+ * 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.launcher3;
+
+public class SpringLoadedDragController implements OnAlarmListener {
+    // how long the user must hover over a mini-screen before it unshrinks
+    final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
+    final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
+    final long EXIT_SPRING_LOAD_HOVER_TIME = 200;
+
+    Alarm mAlarm;
+
+    // the screen the user is currently hovering over, if any
+    private CellLayout mScreen;
+    private Launcher mLauncher;
+
+    public SpringLoadedDragController(Launcher launcher) {
+        mLauncher = launcher;
+        mAlarm = new Alarm();
+        mAlarm.setOnAlarmListener(this);
+    }
+
+    public void cancel() {
+        mAlarm.cancelAlarm();
+    }
+
+    // Set a new alarm to expire for the screen that we are hovering over now
+    public void setAlarm(CellLayout cl) {
+        mAlarm.cancelAlarm();
+        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME :
+            ENTER_SPRING_LOAD_HOVER_TIME);
+        mScreen = cl;
+    }
+
+    // this is called when our timer runs out
+    public void onAlarm(Alarm alarm) {
+        if (mScreen != null) {
+            // Snap to the screen that we are hovering over now
+            Workspace w = mLauncher.getWorkspace();
+            int page = w.indexOfChild(mScreen);
+            if (page != w.getCurrentPage()) {
+                w.snapToPage(page);
+            }
+        } else {
+            mLauncher.getDragController().cancelDrag();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
new file mode 100644
index 0000000..882fb04
--- /dev/null
+++ b/src/com/android/launcher3/Stats.java
@@ -0,0 +1,213 @@
+/*
+ * 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.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+
+public class Stats {
+    private static final boolean DEBUG_BROADCASTS = false;
+    private static final String TAG = "Launcher3/Stats";
+
+    private static final boolean LOCAL_LAUNCH_LOG = true;
+
+    public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
+    public static final String PERM_LAUNCH = "com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS";
+    public static final String EXTRA_INTENT = "intent";
+    public static final String EXTRA_CONTAINER = "container";
+    public static final String EXTRA_SCREEN = "screen";
+    public static final String EXTRA_CELLX = "cellX";
+    public static final String EXTRA_CELLY = "cellY";
+
+    private static final String LOG_FILE_NAME = "launches.log";
+    private static final int LOG_VERSION = 1;
+    private static final int LOG_TAG_VERSION = 0x1;
+    private static final int LOG_TAG_LAUNCH = 0x1000;
+
+    private static final String STATS_FILE_NAME = "stats.log";
+    private static final int STATS_VERSION = 1;
+    private static final int INITIAL_STATS_SIZE = 100;
+
+    // TODO: delayed/batched writes
+    private static final boolean FLUSH_IMMEDIATELY = true;
+
+    private final Launcher mLauncher;
+
+    DataOutputStream mLog;
+
+    ArrayList<String> mIntents;
+    ArrayList<Integer> mHistogram;
+
+    public Stats(Launcher launcher) {
+        mLauncher = launcher;
+
+        loadStats();
+
+        if (LOCAL_LAUNCH_LOG) {
+            try {
+                mLog = new DataOutputStream(mLauncher.openFileOutput(LOG_FILE_NAME, Context.MODE_APPEND));
+                mLog.writeInt(LOG_TAG_VERSION);
+                mLog.writeInt(LOG_VERSION);
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "unable to create stats log: " + e);
+                mLog = null;
+            } catch (IOException e) {
+                Log.e(TAG, "unable to write to stats log: " + e);
+                mLog = null;
+            }
+        }
+
+        if (DEBUG_BROADCASTS) {
+            launcher.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
+                                    + intent.getStringExtra(EXTRA_INTENT));
+                        }
+                    },
+                    new IntentFilter(ACTION_LAUNCH),
+                    PERM_LAUNCH,
+                    null
+            );
+        }
+    }
+
+    public void incrementLaunch(String intentStr) {
+        int pos = mIntents.indexOf(intentStr);
+        if (pos < 0) {
+            mIntents.add(intentStr);
+            mHistogram.add(1);
+        } else {
+            mHistogram.set(pos, mHistogram.get(pos) + 1);
+        }
+    }
+
+    public void recordLaunch(Intent intent) {
+        recordLaunch(intent, null);
+    }
+
+    public void recordLaunch(Intent intent, ShortcutInfo shortcut) {
+        intent = new Intent(intent);
+        intent.setSourceBounds(null);
+
+        final String flat = intent.toUri(0);
+
+        Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
+        if (shortcut != null) {
+            broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
+                    .putExtra(EXTRA_SCREEN, shortcut.screenId)
+                    .putExtra(EXTRA_CELLX, shortcut.cellX)
+                    .putExtra(EXTRA_CELLY, shortcut.cellY);
+        }
+        mLauncher.sendBroadcast(broadcastIntent, PERM_LAUNCH);
+
+        incrementLaunch(flat);
+
+        if (FLUSH_IMMEDIATELY) {
+            saveStats();
+        }
+
+        if (LOCAL_LAUNCH_LOG && mLog != null) {
+            try {
+                mLog.writeInt(LOG_TAG_LAUNCH);
+                mLog.writeLong(System.currentTimeMillis());
+                if (shortcut == null) {
+                    mLog.writeShort(0);
+                    mLog.writeShort(0);
+                    mLog.writeShort(0);
+                    mLog.writeShort(0);
+                } else {
+                    mLog.writeShort((short) shortcut.container);
+                    mLog.writeShort((short) shortcut.screenId);
+                    mLog.writeShort((short) shortcut.cellX);
+                    mLog.writeShort((short) shortcut.cellY);
+                }
+                mLog.writeUTF(flat);
+                if (FLUSH_IMMEDIATELY) {
+                    mLog.flush(); // TODO: delayed writes
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void saveStats() {
+        DataOutputStream stats = null;
+        try {
+            stats = new DataOutputStream(mLauncher.openFileOutput(STATS_FILE_NAME + ".tmp", Context.MODE_PRIVATE));
+            stats.writeInt(STATS_VERSION);
+            final int N = mHistogram.size();
+            stats.writeInt(N);
+            for (int i=0; i<N; i++) {
+                stats.writeUTF(mIntents.get(i));
+                stats.writeInt(mHistogram.get(i));
+            }
+            stats.close();
+            stats = null;
+            mLauncher.getFileStreamPath(STATS_FILE_NAME + ".tmp")
+                     .renameTo(mLauncher.getFileStreamPath(STATS_FILE_NAME));
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "unable to create stats data: " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "unable to write to stats data: " + e);
+        } finally {
+            if (stats != null) {
+                try {
+                    stats.close();
+                } catch (IOException e) { }
+            }
+        }
+    }
+
+    private void loadStats() {
+        mIntents = new ArrayList<String>(INITIAL_STATS_SIZE);
+        mHistogram = new ArrayList<Integer>(INITIAL_STATS_SIZE);
+        DataInputStream stats = null;
+        try {
+            stats = new DataInputStream(mLauncher.openFileInput(STATS_FILE_NAME));
+            final int version = stats.readInt();
+            if (version == STATS_VERSION) {
+                final int N = stats.readInt();
+                for (int i=0; i<N; i++) {
+                    final String pkg = stats.readUTF();
+                    final int count = stats.readInt();
+                    mIntents.add(pkg);
+                    mHistogram.add(count);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // not a problem
+        } catch (IOException e) {
+            // more of a problem
+
+        } finally {
+            if (stats != null) {
+                try {
+                    stats.close();
+                } catch (IOException e) { }
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
new file mode 100644
index 0000000..494694c
--- /dev/null
+++ b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+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.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
+    private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+    private final LayoutInflater mInflater;
+    private final PackageManager mPackageManager;
+    private final int mIconSize;
+
+    private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers =
+            new ArrayList<ThirdPartyWallpaperTile>();
+
+    public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+        private ResolveInfo mResolveInfo;
+        public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
+            mResolveInfo = resolveInfo;
+        }
+        @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);
+            launchIntent.setComponent(itemComponentName);
+            Utilities.startActivityForResultSafely(
+                    a, launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+        }
+    }
+
+    public ThirdPartyWallpaperPickerListAdapter(Context context) {
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPackageManager = context.getPackageManager();
+        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+        final PackageManager pm = mPackageManager;
+
+        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        final List<ResolveInfo> apps =
+                pm.queryIntentActivities(pickWallpaperIntent, 0);
+
+        // Get list of image picker intents
+        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
+        pickImageIntent.setType("image/*");
+        final List<ResolveInfo> imagePickerActivities =
+                pm.queryIntentActivities(pickImageIntent, 0);
+        final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
+        for (int i = 0; i < imagePickerActivities.size(); i++) {
+            ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
+            imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
+        }
+
+        outerLoop:
+        for (ResolveInfo info : apps) {
+            final ComponentName itemComponentName =
+                    new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+            final String itemPackageName = itemComponentName.getPackageName();
+            // Exclude anything from our own package, and the old Launcher,
+            // and live wallpaper picker
+            if (itemPackageName.equals(context.getPackageName()) ||
+                    itemPackageName.equals("com.android.launcher") ||
+                    itemPackageName.equals("com.android.wallpaper.livepicker")) {
+                continue;
+            }
+            // Exclude any package that already responds to the image picker intent
+            for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
+                if (itemPackageName.equals(
+                        imagePickerActivityInfo.activityInfo.packageName)) {
+                    continue outerLoop;
+                }
+            }
+            mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
+        }
+    }
+
+    public int getCount() {
+        return mThirdPartyWallpaperPickers.size();
+    }
+
+    public ThirdPartyWallpaperTile getItem(int position) {
+        return mThirdPartyWallpaperPickers.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view;
+
+        if (convertView == null) {
+            view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+        ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
+        TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+        label.setText(info.loadLabel(mPackageManager));
+        Drawable icon = info.loadIcon(mPackageManager);
+        icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+        label.setCompoundDrawables(null, icon, null, null);
+        return view;
+    }
+}
diff --git a/src/com/android/launcher3/ToggleWeightWatcher.java b/src/com/android/launcher3/ToggleWeightWatcher.java
new file mode 100644
index 0000000..33701a2
--- /dev/null
+++ b/src/com/android/launcher3/ToggleWeightWatcher.java
@@ -0,0 +1,7 @@
+package com.android.launcher3;
+
+import android.app.Activity;
+
+public class ToggleWeightWatcher extends Activity {
+
+}
diff --git a/src/com/android/launcher3/UninstallShortcutReceiver.java b/src/com/android/launcher3/UninstallShortcutReceiver.java
new file mode 100644
index 0000000..ccea4ec
--- /dev/null
+++ b/src/com/android/launcher3/UninstallShortcutReceiver.java
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.widget.Toast;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class UninstallShortcutReceiver extends BroadcastReceiver {
+    private static final String ACTION_UNINSTALL_SHORTCUT =
+            "com.android.launcher.action.UNINSTALL_SHORTCUT";
+
+    // The set of shortcuts that are pending uninstall
+    private static ArrayList<PendingUninstallShortcutInfo> mUninstallQueue =
+            new ArrayList<PendingUninstallShortcutInfo>();
+
+    // Determines whether to defer uninstalling shortcuts immediately until
+    // disableAndFlushUninstallQueue() is called.
+    private static boolean mUseUninstallQueue = false;
+
+    private static class PendingUninstallShortcutInfo {
+        Intent data;
+
+        public PendingUninstallShortcutInfo(Intent rawData) {
+            data = rawData;
+        }
+    }
+
+    public void onReceive(Context context, Intent data) {
+        if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) {
+            return;
+        }
+
+        PendingUninstallShortcutInfo info = new PendingUninstallShortcutInfo(data);
+        if (mUseUninstallQueue) {
+            mUninstallQueue.add(info);
+        } else {
+            processUninstallShortcut(context, info);
+        }
+    }
+
+    static void enableUninstallQueue() {
+        mUseUninstallQueue = true;
+    }
+
+    static void disableAndFlushUninstallQueue(Context context) {
+        mUseUninstallQueue = false;
+        Iterator<PendingUninstallShortcutInfo> iter = mUninstallQueue.iterator();
+        while (iter.hasNext()) {
+            processUninstallShortcut(context, iter.next());
+            iter.remove();
+        }
+    }
+
+    private static void processUninstallShortcut(Context context,
+            PendingUninstallShortcutInfo pendingInfo) {
+        final Intent data = pendingInfo.data;
+
+        LauncherAppState.setApplicationContext(context.getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+        synchronized (app) { // TODO: make removeShortcut internally threadsafe
+            removeShortcut(context, data);
+        }
+    }
+
+    private static void removeShortcut(Context context, Intent data) {
+        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
+
+        if (intent != null && name != null) {
+            final ContentResolver cr = context.getContentResolver();
+            Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
+                LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);
+
+            final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+
+            boolean changed = false;
+
+            try {
+                while (c.moveToNext()) {
+                    try {
+                        if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) {
+                            final long id = c.getLong(idIndex);
+                            final Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
+                            cr.delete(uri, null, null);
+                            changed = true;
+                            if (!duplicate) {
+                                break;
+                            }
+                        }
+                    } catch (URISyntaxException e) {
+                        // Ignore
+                    }
+                }
+            } finally {
+                c.close();
+            }
+
+            if (changed) {
+                cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+                Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/UserInitializeReceiver.java b/src/com/android/launcher3/UserInitializeReceiver.java
new file mode 100644
index 0000000..d8e17b1
--- /dev/null
+++ b/src/com/android/launcher3/UserInitializeReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Takes care of setting initial wallpaper for a user, by selecting the
+ * first wallpaper that is not in use by another user.
+ */
+public class UserInitializeReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // TODO: initial wallpaper now that wallpapers are owned by another app
+    }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
new file mode 100644
index 0000000..2cb9314
--- /dev/null
+++ b/src/com/android/launcher3/Utilities.java
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+/**
+ * Various utilities shared amongst the Launcher's classes.
+ */
+final class Utilities {
+    private static final String TAG = "Launcher.Utilities";
+
+    private static int sIconWidth = -1;
+    private static int sIconHeight = -1;
+    public static int sIconTextureWidth = -1;
+    public static int sIconTextureHeight = -1;
+
+    private static final Paint sBlurPaint = new Paint();
+    private static final Paint sGlowColorPressedPaint = new Paint();
+    private static final Paint sGlowColorFocusedPaint = new Paint();
+    private static final Paint sDisabledPaint = new Paint();
+    private static final Rect sOldBounds = new Rect();
+    private static final Canvas sCanvas = new Canvas();
+
+    static {
+        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+                Paint.FILTER_BITMAP_FLAG));
+    }
+    static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
+    static int sColorIndex = 0;
+
+    /**
+     * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
+     * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
+     * to the proper size (48dp)
+     */
+    static Bitmap createIconBitmap(Bitmap icon, Context context) {
+        int textureWidth = sIconTextureWidth;
+        int textureHeight = sIconTextureHeight;
+        int sourceWidth = icon.getWidth();
+        int sourceHeight = icon.getHeight();
+        if (sourceWidth > textureWidth && sourceHeight > textureHeight) {
+            // Icon is bigger than it should be; clip it (solves the GB->ICS migration case)
+            return Bitmap.createBitmap(icon,
+                    (sourceWidth - textureWidth) / 2,
+                    (sourceHeight - textureHeight) / 2,
+                    textureWidth, textureHeight);
+        } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) {
+            // Icon is the right size, no need to change it
+            return icon;
+        } else {
+            // Icon is too small, render to a larger bitmap
+            final Resources resources = context.getResources();
+            return createIconBitmap(new BitmapDrawable(resources, icon), context);
+        }
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view.
+     */
+    static Bitmap createIconBitmap(Drawable icon, Context context) {
+        synchronized (sCanvas) { // we share the statics :-(
+            if (sIconWidth == -1) {
+                initStatics(context);
+            }
+
+            int width = sIconWidth;
+            int height = sIconHeight;
+
+            if (icon instanceof PaintDrawable) {
+                PaintDrawable painter = (PaintDrawable) icon;
+                painter.setIntrinsicWidth(width);
+                painter.setIntrinsicHeight(height);
+            } else if (icon instanceof BitmapDrawable) {
+                // Ensure the bitmap has a density.
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+                Bitmap bitmap = bitmapDrawable.getBitmap();
+                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+                }
+            }
+            int sourceWidth = icon.getIntrinsicWidth();
+            int sourceHeight = icon.getIntrinsicHeight();
+            if (sourceWidth > 0 && sourceHeight > 0) {
+                // Scale the icon proportionally to the icon dimensions
+                final float ratio = (float) sourceWidth / sourceHeight;
+                if (sourceWidth > sourceHeight) {
+                    height = (int) (width / ratio);
+                } else if (sourceHeight > sourceWidth) {
+                    width = (int) (height * ratio);
+                }
+            }
+
+            // no intrinsic size --> use default size
+            int textureWidth = sIconTextureWidth;
+            int textureHeight = sIconTextureHeight;
+
+            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+                    Bitmap.Config.ARGB_8888);
+            final Canvas canvas = sCanvas;
+            canvas.setBitmap(bitmap);
+
+            final int left = (textureWidth-width) / 2;
+            final int top = (textureHeight-height) / 2;
+
+            @SuppressWarnings("all") // suppress dead code warning
+            final boolean debug = false;
+            if (debug) {
+                // draw a big box for the icon for debugging
+                canvas.drawColor(sColors[sColorIndex]);
+                if (++sColorIndex >= sColors.length) sColorIndex = 0;
+                Paint debugPaint = new Paint();
+                debugPaint.setColor(0xffcccc00);
+                canvas.drawRect(left, top, left+width, top+height, debugPaint);
+            }
+
+            sOldBounds.set(icon.getBounds());
+            icon.setBounds(left, top, left+width, top+height);
+            icon.draw(canvas);
+            icon.setBounds(sOldBounds);
+            canvas.setBitmap(null);
+
+            return bitmap;
+        }
+    }
+
+    /**
+     * Returns a Bitmap representing the thumbnail of the specified Bitmap.
+     *
+     * @param bitmap The bitmap to get a thumbnail of.
+     * @param context The application's context.
+     *
+     * @return A thumbnail for the specified bitmap or the bitmap itself if the
+     *         thumbnail could not be created.
+     */
+    static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) {
+        synchronized (sCanvas) { // we share the statics :-(
+            if (sIconWidth == -1) {
+                initStatics(context);
+            }
+
+            if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) {
+                return bitmap;
+            } else {
+                final Resources resources = context.getResources();
+                return createIconBitmap(new BitmapDrawable(resources, bitmap), context);
+            }
+        }
+    }
+
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in a parent view's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param root The root view to make the coordinates relative to.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the descendant:
+     *          sometimes this is relevant as in a child's coordinates within the descendant.
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public static float getDescendantCoordRelativeToParent(View descendant, View root,
+                                                           int[] coord, boolean includeRootScroll) {
+        ArrayList<View> ancestorChain = new ArrayList<View>();
+
+        float[] pt = {coord[0], coord[1]};
+
+        View v = descendant;
+        while(v != root && v != null) {
+            ancestorChain.add(v);
+            v = (View) v.getParent();
+        }
+        ancestorChain.add(root);
+
+        float scale = 1.0f;
+        int count = ancestorChain.size();
+        for (int i = 0; i < count; i++) {
+            View v0 = ancestorChain.get(i);
+            // For TextViews, scroll has a meaning which relates to the text position
+            // which is very strange... ignore the scroll.
+            if (v0 != descendant || includeRootScroll) {
+                pt[0] -= v0.getScrollX();
+                pt[1] -= v0.getScrollY();
+            }
+
+            v0.getMatrix().mapPoints(pt);
+            pt[0] += v0.getLeft();
+            pt[1] += v0.getTop();
+            scale *= v0.getScaleX();
+        }
+
+        coord[0] = (int) Math.round(pt[0]);
+        coord[1] = (int) Math.round(pt[1]);
+        return scale;
+    }
+
+    /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+     */
+    public static float mapCoordInSelfToDescendent(View descendant, View root,
+                                                   int[] coord) {
+        ArrayList<View> ancestorChain = new ArrayList<View>();
+
+        float[] pt = {coord[0], coord[1]};
+
+        View v = descendant;
+        while(v != root) {
+            ancestorChain.add(v);
+            v = (View) v.getParent();
+        }
+        ancestorChain.add(root);
+
+        float scale = 1.0f;
+        Matrix inverse = new Matrix();
+        int count = ancestorChain.size();
+        for (int i = count - 1; i >= 0; i--) {
+            View ancestor = ancestorChain.get(i);
+            View next = i > 0 ? ancestorChain.get(i-1) : null;
+
+            pt[0] += ancestor.getScrollX();
+            pt[1] += ancestor.getScrollY();
+
+            if (next != null) {
+                pt[0] -= next.getLeft();
+                pt[1] -= next.getTop();
+                next.getMatrix().invert(inverse);
+                inverse.mapPoints(pt);
+                scale *= next.getScaleX();
+            }
+        }
+
+        coord[0] = (int) Math.round(pt[0]);
+        coord[1] = (int) Math.round(pt[1]);
+        return scale;
+    }
+
+    private static void initStatics(Context context) {
+        final Resources resources = context.getResources();
+        final DisplayMetrics metrics = resources.getDisplayMetrics();
+        final float density = metrics.density;
+
+        sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
+        sIconTextureWidth = sIconTextureHeight = sIconWidth;
+
+        sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL));
+        sGlowColorPressedPaint.setColor(0xffffc300);
+        sGlowColorFocusedPaint.setColor(0xffff8e00);
+
+        ColorMatrix cm = new ColorMatrix();
+        cm.setSaturation(0.2f);
+        sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm));
+        sDisabledPaint.setAlpha(0x88);
+    }
+
+    public static void setIconSize(int widthPx) {
+        sIconWidth = sIconHeight = widthPx;
+        sIconTextureWidth = sIconTextureHeight = widthPx;
+    }
+
+    public static void scaleRect(Rect r, float scale) {
+        if (scale != 1.0f) {
+            r.left = (int) (r.left * scale + 0.5f);
+            r.top = (int) (r.top * scale + 0.5f);
+            r.right = (int) (r.right * scale + 0.5f);
+            r.bottom = (int) (r.bottom * scale + 0.5f);
+        }
+    }
+
+    public static void scaleRectAboutCenter(Rect r, float scale) {
+        int cx = r.centerX();
+        int cy = r.centerY();
+        r.offset(-cx, -cy);
+        Utilities.scaleRect(r, scale);
+        r.offset(cx, cy);
+    }
+
+    public static void startActivityForResultSafely(
+            Activity activity, Intent intent, int requestCode) {
+        try {
+            activity.startActivityForResult(intent, requestCode);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+        } catch (SecurityException e) {
+            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+                    "or use the exported attribute for this activity.", e);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
new file mode 100644
index 0000000..79cc88e
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -0,0 +1,749 @@
+/*
+ * 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.launcher3;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+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.os.AsyncTask;
+import android.os.Bundle;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperCropActivity extends Activity {
+    private static final String LOGTAG = "Launcher3.CropActivity";
+
+    protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+    protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+    private static final int DEFAULT_COMPRESS_QUALITY = 90;
+    /**
+     * The maximum bitmap size we allow to be returned through the intent.
+     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+     * have some overhead to hit so that we go way below the limit here to make
+     * sure the intent stays below 1MB.We should consider just returning a byte
+     * array instead of a Bitmap instance to avoid overhead.
+     */
+    public static final int MAX_BMAP_IN_INTENT = 750000;
+    private static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+    protected CropView mCropView;
+    protected Uri mUri;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        init();
+        if (!enableRotation()) {
+            setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
+        }
+    }
+
+    protected void init() {
+        setContentView(R.layout.wallpaper_cropper);
+
+        mCropView = (CropView) findViewById(R.id.cropView);
+
+        Intent cropIntent = this.getIntent();
+        final Uri imageUri = cropIntent.getData();
+
+        int rotation = getRotationFromExif(this, imageUri);
+        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
+        mCropView.setTouchEnabled(true);
+        // 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) {
+                        boolean finishActivityWhenDone = true;
+                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
+                    }
+                });
+    }
+
+    public boolean enableRotation() {
+        return getResources().getBoolean(R.bool.allow_rotation);
+    }
+
+    public static String getSharedPreferencesKey() {
+        return WallpaperCropActivity.class.getName();
+    }
+
+    // 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;
+    }
+
+    static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
+        Point minDims = new Point();
+        Point maxDims = new Point();
+        windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+        int maxDim = Math.max(maxDims.x, maxDims.y);
+        int minDim = Math.max(minDims.x, minDims.y);
+
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            Point realSize = new Point();
+            windowManager.getDefaultDisplay().getRealSize(realSize);
+            maxDim = Math.max(realSize.x, realSize.y);
+            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 (isScreenLarge(res)) {
+            defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+            defaultHeight = maxDim;
+        } else {
+            defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+            defaultHeight = maxDim;
+        }
+        return new Point(defaultWidth, defaultHeight);
+    }
+
+    public static int getRotationFromExif(String path) {
+        return getRotationFromExifHelper(path, null, 0, null, null);
+    }
+
+    public static int getRotationFromExif(Context context, Uri uri) {
+        return getRotationFromExifHelper(null, null, 0, context, uri);
+    }
+
+    public static int getRotationFromExif(Resources res, int resId) {
+        return getRotationFromExifHelper(null, res, resId, null, null);
+    }
+
+    private static int getRotationFromExifHelper(
+            String path, Resources res, int resId, Context context, Uri uri) {
+        ExifInterface ei = new ExifInterface();
+        try {
+            if (path != null) {
+                ei.readExif(path);
+            } else if (uri != null) {
+                InputStream is = context.getContentResolver().openInputStream(uri);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                ei.readExif(bis);
+            } else {
+                InputStream is = res.openRawResource(resId);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                ei.readExif(bis);
+            }
+            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+            if (ori != null) {
+                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
+            }
+        } catch (IOException e) {
+            Log.w(LOGTAG, "Getting exif data failed", e);
+        }
+        return 0;
+    }
+
+    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
+        int rotation = getRotationFromExif(filePath);
+        BitmapCropTask cropTask = new BitmapCropTask(
+                this, filePath, null, rotation, 0, 0, true, false, null);
+        final Point bounds = cropTask.getImageBounds();
+        Runnable onEndCrop = new Runnable() {
+            public void run() {
+                updateWallpaperDimensions(bounds.x, bounds.y);
+                if (finishActivityWhenDone) {
+                    setResult(Activity.RESULT_OK);
+                    finish();
+                }
+            }
+        };
+        cropTask.setOnEndRunnable(onEndCrop);
+        cropTask.setNoCrop(true);
+        cropTask.execute();
+    }
+
+    protected void cropImageAndSetWallpaper(
+            Resources res, int resId, final boolean finishActivityWhenDone) {
+        // crop this image and scale it down to the default wallpaper size for
+        // this device
+        int rotation = getRotationFromExif(res, resId);
+        Point inSize = mCropView.getSourceDimensions();
+        Point outSize = getDefaultWallpaperSize(getResources(),
+                getWindowManager());
+        RectF crop = getMaxCropRect(
+                inSize.x, inSize.y, outSize.x, outSize.y, false);
+        Runnable onEndCrop = new Runnable() {
+            public void run() {
+                // Passing 0, 0 will cause launcher to revert to using the
+                // default wallpaper size
+                updateWallpaperDimensions(0, 0);
+                if (finishActivityWhenDone) {
+                    setResult(Activity.RESULT_OK);
+                    finish();
+                }
+            }
+        };
+        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
+                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
+        cropTask.execute();
+    }
+
+    private static boolean isScreenLarge(Resources res) {
+        Configuration config = res.getConfiguration();
+        return config.smallestScreenWidthDp >= 720;
+    }
+
+    protected void cropImageAndSetWallpaper(Uri uri,
+            OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+        // Get the crop
+        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+        Point minDims = new Point();
+        Point maxDims = new Point();
+        Display d = getWindowManager().getDefaultDisplay();
+        d.getCurrentSizeRange(minDims, maxDims);
+
+        Point displaySize = new Point();
+        d.getSize(displaySize);
+
+        int maxDim = Math.max(maxDims.x, maxDims.y);
+        final int minDim = Math.min(minDims.x, minDims.y);
+        int defaultWallpaperWidth;
+        if (isScreenLarge(getResources())) {
+            defaultWallpaperWidth = (int) (maxDim *
+                    wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+        } else {
+            defaultWallpaperWidth = Math.max((int)
+                    (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+        }
+
+        boolean isPortrait = displaySize.x < displaySize.y;
+        int portraitHeight;
+        if (isPortrait) {
+            portraitHeight = mCropView.getHeight();
+        } else {
+            // TODO: how to actually get the proper portrait height?
+            // This is not quite right:
+            portraitHeight = Math.max(maxDims.x, maxDims.y);
+        }
+        if (android.os.Build.VERSION.SDK_INT >=
+                android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            Point realSize = new Point();
+            d.getRealSize(realSize);
+            portraitHeight = Math.max(realSize.x, realSize.y);
+        }
+        // Get the crop
+        RectF cropRect = mCropView.getCrop();
+        int cropRotation = mCropView.getImageRotation();
+        float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+        Point inSize = mCropView.getSourceDimensions();
+        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]);
+
+        // 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 = defaultWallpaperWidth / 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 + portraitHeight / cropScale;
+        } else { // LANDSCAPE
+            float extraPortraitHeight =
+                    portraitHeight / 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);
+
+        Runnable onEndCrop = new Runnable() {
+            public void run() {
+                updateWallpaperDimensions(outWidth, outHeight);
+                if (finishActivityWhenDone) {
+                    setResult(Activity.RESULT_OK);
+                    finish();
+                }
+            }
+        };
+        BitmapCropTask cropTask = new BitmapCropTask(this, uri,
+                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
+        if (onBitmapCroppedHandler != null) {
+            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+        }
+        cropTask.execute();
+    }
+
+    public interface OnBitmapCroppedHandler {
+        public void onBitmapCropped(byte[] imageBytes);
+    }
+
+    protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
+        Uri mInUri = null;
+        Context mContext;
+        String mInFilePath;
+        byte[] mInImageBytes;
+        int mInResId = 0;
+        InputStream mInStream;
+        RectF mCropBounds = null;
+        int mOutWidth, mOutHeight;
+        int mRotation;
+        String mOutputFormat = "jpg"; // for now
+        boolean mSetWallpaper;
+        boolean mSaveCroppedBitmap;
+        Bitmap mCroppedBitmap;
+        Runnable mOnEndRunnable;
+        Resources mResources;
+        OnBitmapCroppedHandler mOnBitmapCroppedHandler;
+        boolean mNoCrop;
+
+        public BitmapCropTask(Context c, String filePath,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
+                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mContext = c;
+            mInFilePath = filePath;
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+        }
+
+        public BitmapCropTask(byte[] imageBytes,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
+                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mInImageBytes = imageBytes;
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+        }
+
+        public BitmapCropTask(Context c, Uri inUri,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
+                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mContext = c;
+            mInUri = inUri;
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+        }
+
+        public BitmapCropTask(Context c, Resources res, int inResId,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
+                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mContext = c;
+            mInResId = inResId;
+            mResources = res;
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+        }
+
+        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
+                boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+            mCropBounds = cropBounds;
+            mRotation = rotation;
+            mOutWidth = outWidth;
+            mOutHeight = outHeight;
+            mSetWallpaper = setWallpaper;
+            mSaveCroppedBitmap = saveCroppedBitmap;
+            mOnEndRunnable = onEndRunnable;
+        }
+
+        public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
+            mOnBitmapCroppedHandler = handler;
+        }
+
+        public void setNoCrop(boolean value) {
+            mNoCrop = value;
+        }
+
+        public void setOnEndRunnable(Runnable onEndRunnable) {
+            mOnEndRunnable = onEndRunnable;
+        }
+
+        // Helper to setup input stream
+        private void regenerateInputStream() {
+            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
+                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
+                        "image byte array given");
+            } else {
+                Utils.closeSilently(mInStream);
+                try {
+                    if (mInUri != null) {
+                        mInStream = new BufferedInputStream(
+                                mContext.getContentResolver().openInputStream(mInUri));
+                    } else if (mInFilePath != null) {
+                        mInStream = mContext.openFileInput(mInFilePath);
+                    } else if (mInImageBytes != null) {
+                        mInStream = new BufferedInputStream(
+                                new ByteArrayInputStream(mInImageBytes));
+                    } else {
+                        mInStream = new BufferedInputStream(
+                                mResources.openRawResource(mInResId));
+                    }
+                } catch (FileNotFoundException e) {
+                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+                }
+            }
+        }
+
+        public Point getImageBounds() {
+            regenerateInputStream();
+            if (mInStream != null) {
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inJustDecodeBounds = true;
+                BitmapFactory.decodeStream(mInStream, null, options);
+                if (options.outWidth != 0 && options.outHeight != 0) {
+                    return new Point(options.outWidth, options.outHeight);
+                }
+            }
+            return null;
+        }
+
+        public void setCropBounds(RectF cropBounds) {
+            mCropBounds = cropBounds;
+        }
+
+        public Bitmap getCroppedBitmap() {
+            return mCroppedBitmap;
+        }
+        public boolean cropBitmap() {
+            boolean failure = false;
+
+            regenerateInputStream();
+
+            WallpaperManager wallpaperManager = null;
+            if (mSetWallpaper) {
+                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
+            }
+            if (mSetWallpaper && mNoCrop && mInStream != null) {
+                try {
+                    wallpaperManager.setStream(mInStream);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+                    failure = true;
+                }
+                return !failure;
+            }
+            if (mInStream != null) {
+                // Find crop bounds (scaled to original image size)
+                Rect roundedTrueCrop = new Rect();
+                Matrix rotateMatrix = new Matrix();
+                Matrix inverseRotateMatrix = new Matrix();
+                if (mRotation > 0) {
+                    rotateMatrix.setRotate(mRotation);
+                    inverseRotateMatrix.setRotate(-mRotation);
+
+                    mCropBounds.roundOut(roundedTrueCrop);
+                    mCropBounds = new RectF(roundedTrueCrop);
+
+                    Point bounds = getImageBounds();
+
+                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+                    rotateMatrix.mapPoints(rotatedBounds);
+                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+                    inverseRotateMatrix.mapRect(mCropBounds);
+                    mCropBounds.offset(bounds.x/2, bounds.y/2);
+
+                    regenerateInputStream();
+                }
+
+                mCropBounds.roundOut(roundedTrueCrop);
+
+                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+                    Log.w(LOGTAG, "crop has bad values for full size image");
+                    failure = true;
+                    return false;
+                }
+
+                // See how much we're reducing the size of the image
+                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
+                        roundedTrueCrop.height() / mOutHeight);
+
+                // Attempt to open a region decoder
+                BitmapRegionDecoder decoder = null;
+                try {
+                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                }
+
+                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
+                    regenerateInputStream();
+                    Bitmap fullSize = null;
+                    if (mInStream != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        if (scaleDownSampleSize > 1) {
+                            options.inSampleSize = scaleDownSampleSize;
+                        }
+                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
+                    }
+                    if (fullSize != null) {
+                        mCropBounds.left /= scaleDownSampleSize;
+                        mCropBounds.top /= scaleDownSampleSize;
+                        mCropBounds.bottom /= scaleDownSampleSize;
+                        mCropBounds.right /= scaleDownSampleSize;
+                        mCropBounds.roundOut(roundedTrueCrop);
+
+                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+                                roundedTrueCrop.top, roundedTrueCrop.width(),
+                                roundedTrueCrop.height());
+                    }
+                }
+
+                if (crop == null) {
+                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+                    failure = true;
+                    return false;
+                }
+                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 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 (!(mOutWidth > 0 && mOutHeight > 0)) {
+                        mOutWidth = Math.round(dimsAfter[0]);
+                        mOutHeight = Math.round(dimsAfter[1]);
+                    }
+
+                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
+                    RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
+
+                    Matrix m = new Matrix();
+                    if (mRotation == 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(mRotation);
+                        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;
+                    }
+                }
+
+                if (mSaveCroppedBitmap) {
+                    mCroppedBitmap = crop;
+                }
+
+                // Get output compression format
+                CompressFormat cf =
+                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+                // Compress to byte array
+                ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+                if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+                    // If we need to set to the wallpaper, set it
+                    if (mSetWallpaper && wallpaperManager != null) {
+                        try {
+                            byte[] outByteArray = tmpOut.toByteArray();
+                            wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
+                            if (mOnBitmapCroppedHandler != null) {
+                                mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
+                            }
+                        } catch (IOException e) {
+                            Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+                            failure = true;
+                        }
+                    }
+                } else {
+                    Log.w(LOGTAG, "cannot compress bitmap");
+                    failure = true;
+                }
+            }
+            return !failure; // True if any of the operations failed
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            return cropBitmap();
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            if (mOnEndRunnable != null) {
+                mOnEndRunnable.run();
+            }
+        }
+    }
+
+    protected void updateWallpaperDimensions(int width, int height) {
+        String spKey = getSharedPreferencesKey();
+        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        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();
+
+        suggestWallpaperDimension(getResources(),
+                sp, getWindowManager(), WallpaperManager.getInstance(this));
+    }
+
+    static public void suggestWallpaperDimension(Resources res,
+            final SharedPreferences sharedPrefs,
+            WindowManager windowManager,
+            final WallpaperManager wallpaperManager) {
+        final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
+
+        new Thread("suggestWallpaperDimension") {
+            public void run() {
+                // If we have saved a wallpaper width/height, use that instead
+                int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
+                int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
+                wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+            }
+        }.start();
+    }
+
+    protected 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;
+    }
+
+    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
+        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+    }
+
+    protected static String getFileExtension(String requestFormat) {
+        String outputFormat = (requestFormat == null)
+                ? "jpg"
+                : requestFormat;
+        outputFormat = outputFormat.toLowerCase();
+        return (outputFormat.equals("png") || outputFormat.equals("gif"))
+                ? "png" // We don't support gif compression.
+                : "jpg";
+    }
+}
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
new file mode 100644
index 0000000..9bf3c77
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -0,0 +1,847 @@
+/*
+ * 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.launcher3;
+
+import android.animation.LayoutTransition;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+public class WallpaperPickerActivity extends WallpaperCropActivity {
+    static final String TAG = "Launcher.WallpaperPickerActivity";
+
+    public static final int IMAGE_PICK = 5;
+    public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+    public static final int PICK_LIVE_WALLPAPER = 7;
+    private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
+
+    private View mSelectedThumb;
+    private boolean mIgnoreNextTap;
+    private OnClickListener mThumbnailOnClickListener;
+
+    private LinearLayout mWallpapersView;
+    private View mWallpaperStrip;
+
+    private ActionMode.Callback mActionModeCallback;
+    private ActionMode mActionMode;
+
+    private View.OnLongClickListener mLongClickListener;
+
+    ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
+    private SavedWallpaperImages mSavedImages;
+    private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
+
+    public static abstract class WallpaperTileInfo {
+        protected View mView;
+        public void setView(View v) {
+            mView = v;
+        }
+        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 static class PickImageInfo extends WallpaperTileInfo {
+        @Override
+        public void onClick(WallpaperPickerActivity a) {
+            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+            intent.setType("image/*");
+            Utilities.startActivityForResultSafely(a, intent, IMAGE_PICK);
+        }
+    }
+
+    public static class UriWallpaperInfo extends WallpaperTileInfo {
+        private Uri mUri;
+        public UriWallpaperInfo(Uri uri) {
+            mUri = uri;
+        }
+        @Override
+        public void onClick(WallpaperPickerActivity a) {
+            CropView v = a.getCropView();
+            int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
+            v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
+            v.setTouchEnabled(true);
+        }
+        @Override
+        public void onSave(final WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
+                public void onBitmapCropped(byte[] imageBytes) {
+                    Point thumbSize = getDefaultThumbnailSize(a.getResources());
+                    // rotation is set to 0 since imageBytes has already been correctly rotated
+                    Bitmap thumb = createThumbnail(
+                            thumbSize, null, null, imageBytes, null, 0, 0, true);
+                    a.getSavedImages().writeImage(thumb, imageBytes);
+                }
+            };
+            a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
+        }
+        @Override
+        public boolean isSelectable() {
+            return true;
+        }
+        @Override
+        public boolean isNamelessWallpaper() {
+            return true;
+        }
+    }
+
+    public static class ResourceWallpaperInfo extends WallpaperTileInfo {
+        private Resources mResources;
+        private int mResId;
+        private Drawable mThumb;
+
+        public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+            mResources = res;
+            mResId = resId;
+            mThumb = thumb;
+        }
+        @Override
+        public void onClick(WallpaperPickerActivity a) {
+            int rotation = WallpaperCropActivity.getRotationFromExif(mResources, mResId);
+            BitmapRegionTileSource source = new BitmapRegionTileSource(
+                    mResources, a, mResId, 1024, rotation);
+            CropView v = a.getCropView();
+            v.setTileSource(source, null);
+            Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
+                    a.getResources(), a.getWindowManager());
+            RectF crop = WallpaperCropActivity.getMaxCropRect(
+                    source.getImageWidth(), source.getImageHeight(),
+                    wallpaperSize.x, wallpaperSize.y, false);
+            v.setScale(wallpaperSize.x / crop.width());
+            v.setTouchEnabled(false);
+        }
+        @Override
+        public void onSave(WallpaperPickerActivity a) {
+            boolean finishActivityWhenDone = true;
+            a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
+        }
+        @Override
+        public boolean isSelectable() {
+            return true;
+        }
+        @Override
+        public boolean isNamelessWallpaper() {
+            return true;
+        }
+    }
+
+    public void setWallpaperStripYOffset(float offset) {
+        mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
+    }
+
+    // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+    protected void init() {
+        setContentView(R.layout.wallpaper_picker);
+
+        mCropView = (CropView) findViewById(R.id.cropView);
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        mCropView.setTouchCallback(new CropView.TouchCallback() {
+            LauncherViewPropertyAnimator mAnim;
+            @Override
+            public void onTouchDown() {
+                if (mAnim != null) {
+                    mAnim.cancel();
+                }
+                if (mWallpaperStrip.getTranslationY() == 0) {
+                    mIgnoreNextTap = true;
+                }
+                mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
+                mAnim.translationY(mWallpaperStrip.getHeight()).alpha(0f)
+                        .setInterpolator(new DecelerateInterpolator(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();
+                    }
+                    mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
+                    mAnim.translationY(0f).alpha(1f)
+                            .setInterpolator(new DecelerateInterpolator(0.75f));
+                    mAnim.start();
+                }
+            }
+        });
+
+        mThumbnailOnClickListener = new OnClickListener() {
+            public void onClick(View v) {
+                if (mActionMode != null) {
+                    // When CAB is up, clicking toggles the item instead
+                    if (v.isLongClickable()) {
+                        mLongClickListener.onLongClick(v);
+                    }
+                    return;
+                }
+                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+                if (info.isSelectable()) {
+                    if (mSelectedThumb != null) {
+                        mSelectedThumb.setSelected(false);
+                        mSelectedThumb = null;
+                    }
+                    mSelectedThumb = v;
+                    v.setSelected(true);
+                    // TODO: Remove this once the accessibility framework and
+                    // services have better support for selection state.
+                    v.announceForAccessibility(
+                            getString(R.string.announce_selection, v.getContentDescription()));
+                }
+                info.onClick(WallpaperPickerActivity.this);
+            }
+        };
+        mLongClickListener = new View.OnLongClickListener() {
+            // Called when the user long-clicks on someView
+            public boolean onLongClick(View view) {
+                CheckableFrameLayout c = (CheckableFrameLayout) view;
+                c.toggle();
+
+                if (mActionMode != null) {
+                    mActionMode.invalidate();
+                } else {
+                    // Start the CAB using the ActionMode.Callback defined below
+                    mActionMode = startActionMode(mActionModeCallback);
+                    int childCount = mWallpapersView.getChildCount();
+                    for (int i = 0; i < childCount; i++) {
+                        mWallpapersView.getChildAt(i).setSelected(false);
+                    }
+                }
+                return true;
+            }
+        };
+
+        // Populate the built-in wallpapers
+        ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
+        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+        BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
+        populateWallpapersFromAdapter(mWallpapersView, ia, false, true);
+
+        // Populate the saved wallpapers
+        mSavedImages = new SavedWallpaperImages(this);
+        mSavedImages.loadThumbnailsAndImageIdList();
+        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
+
+        // Populate the live wallpapers
+        final LinearLayout liveWallpapersView =
+                (LinearLayout) findViewById(R.id.live_wallpaper_list);
+        final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
+        a.registerDataSetObserver(new DataSetObserver() {
+            public void onChanged() {
+                liveWallpapersView.removeAllViews();
+                populateWallpapersFromAdapter(liveWallpapersView, a, false, false);
+                initializeScrollForRtl();
+                updateTileIndices();
+            }
+        });
+
+        // Populate the third-party wallpaper pickers
+        final LinearLayout thirdPartyWallpapersView =
+                (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
+        final ThirdPartyWallpaperPickerListAdapter ta =
+                new ThirdPartyWallpaperPickerListAdapter(this);
+        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false, false);
+
+        // Add a tile for the Gallery
+        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+        FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
+                inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
+        setWallpaperItemPaddingToZero(pickImageTile);
+        masterWallpaperList.addView(pickImageTile, 0);
+
+        // Make its background the last photo taken on external storage
+        Bitmap lastPhoto = getThumbnailOfLastPhoto();
+        if (lastPhoto != null) {
+            ImageView galleryThumbnailBg =
+                    (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
+            galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
+            int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+
+        }
+
+        PickImageInfo pickImageInfo = new PickImageInfo();
+        pickImageTile.setTag(pickImageInfo);
+        pickImageInfo.setView(pickImageTile);
+        pickImageTile.setOnClickListener(mThumbnailOnClickListener);
+        pickImageInfo.setView(pickImageTile);
+
+        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) {
+                        if (mSelectedThumb != null) {
+                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
+                            info.onSave(WallpaperPickerActivity.this);
+                        }
+                    }
+                });
+
+        // CAB for deleting items
+        mActionModeCallback = new ActionMode.Callback() {
+            // 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;
+            }
+
+            private int numCheckedItems() {
+                int childCount = mWallpapersView.getChildCount();
+                int numCheckedItems = 0;
+                for (int i = 0; i < childCount; i++) {
+                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+                    if (c.isChecked()) {
+                        numCheckedItems++;
+                    }
+                }
+                return numCheckedItems;
+            }
+
+            // 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 numCheckedItems = 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>();
+                    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);
+                        }
+                    }
+                    for (View v : viewsToRemove) {
+                        mWallpapersView.removeView(v);
+                    }
+                    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);
+                }
+                mSelectedThumb.setSelected(true);
+                mActionMode = null;
+            }
+        };
+    }
+
+    private void initializeScrollForRtl() {
+        final HorizontalScrollView scroll =
+                (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+
+        if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            final ViewTreeObserver observer = scroll.getViewTreeObserver();
+            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                public void onGlobalLayout() {
+                    LinearLayout masterWallpaperList =
+                            (LinearLayout) findViewById(R.id.master_wallpaper_list);
+                    scroll.scrollTo(masterWallpaperList.getWidth(), 0);
+                    scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                }
+            });
+        }
+    }
+
+    public boolean enableRotation() {
+        return super.enableRotation() || Launcher.sForceEnableRotation;
+    }
+
+    protected Bitmap getThumbnailOfLastPhoto() {
+        Cursor cursor = MediaStore.Images.Media.query(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.moveToNext()) {
+            int id = cursor.getInt(0);
+            thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
+                    id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+        }
+        cursor.close();
+        return thumb;
+    }
+
+    protected void onStop() {
+        super.onStop();
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        if (mWallpaperStrip.getTranslationY() > 0f) {
+            mWallpaperStrip.setTranslationY(0f);
+            mWallpaperStrip.setAlpha(1f);
+        }
+    }
+
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
+    }
+
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
+        for (Uri uri : uris) {
+            addTemporaryWallpaperTile(uri);
+        }
+    }
+
+    private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
+            boolean addLongPressHandler, boolean selectFirstTile) {
+        for (int i = 0; i < adapter.getCount(); i++) {
+            FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
+            parent.addView(thumbnail, i);
+            WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
+            thumbnail.setTag(info);
+            info.setView(thumbnail);
+            if (addLongPressHandler) {
+                addLongPressHandler(thumbnail);
+            }
+            thumbnail.setOnClickListener(mThumbnailOnClickListener);
+            if (i == 0 && selectFirstTile) {
+                mThumbnailOnClickListener.onClick(thumbnail);
+            }
+        }
+    }
+
+    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 static Point getDefaultThumbnailSize(Resources res) {
+        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+    }
+
+    private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
+            Resources res, int resId, int rotation, boolean leftAligned) {
+        int width = size.x;
+        int height = size.y;
+
+        BitmapCropTask cropTask;
+        if (uri != null) {
+            cropTask = new BitmapCropTask(
+                    context, uri, null, rotation, width, height, false, true, null);
+        } else if (imageBytes != null) {
+            cropTask = new BitmapCropTask(
+                    imageBytes, null, rotation, width, height, false, true, null);
+        }  else {
+            cropTask = new BitmapCropTask(
+                    context, res, resId, null, rotation, width, height, false, true, null);
+        }
+        Point bounds = cropTask.getImageBounds();
+        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
+            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 = WallpaperCropActivity.getMaxCropRect(
+                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+        cropTask.setCropBounds(cropRect);
+
+        if (cropTask.cropBitmap()) {
+            return cropTask.getCroppedBitmap();
+        } else {
+            return null;
+        }
+    }
+
+    private void addTemporaryWallpaperTile(Uri uri) {
+        mTempWallpaperTiles.add(uri);
+        // Add a tile for the image picked from Gallery
+        FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
+                inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
+        setWallpaperItemPaddingToZero(pickedImageThumbnail);
+
+        // Load the thumbnail
+        ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
+        Point defaultSize = getDefaultThumbnailSize(this.getResources());
+        int rotation = WallpaperCropActivity.getRotationFromExif(this, uri);
+        Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, rotation, false);
+        if (thumb != null) {
+            image.setImageBitmap(thumb);
+            Drawable thumbDrawable = image.getDrawable();
+            thumbDrawable.setDither(true);
+        } else {
+            Log.e(TAG, "Error loading thumbnail for uri=" + uri);
+        }
+        mWallpapersView.addView(pickedImageThumbnail, 0);
+
+        UriWallpaperInfo info = new UriWallpaperInfo(uri);
+        pickedImageThumbnail.setTag(info);
+        info.setView(pickedImageThumbnail);
+        addLongPressHandler(pickedImageThumbnail);
+        updateTileIndices();
+        pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
+        mThumbnailOnClickListener.onClick(pickedImageThumbnail);
+    }
+
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
+            if (data != null && data.getData() != null) {
+                Uri uri = data.getData();
+                addTemporaryWallpaperTile(uri);
+            }
+        } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
+            setResult(RESULT_OK);
+            finish();
+        } else if (requestCode == PICK_LIVE_WALLPAPER) {
+            WallpaperManager wm = WallpaperManager.getInstance(this);
+            final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
+            WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
+            // Try to figure out if a live wallpaper was set;
+            if (newLiveWallpaper != null &&
+                    (oldLiveWallpaper == null ||
+                    !oldLiveWallpaper.getComponent().equals(newLiveWallpaper.getComponent()))) {
+                // Return if a live wallpaper was set
+                setResult(RESULT_OK);
+                finish();
+            }
+        }
+    }
+
+    static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
+        frameLayout.setPadding(0, 0, 0, 0);
+        frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
+    }
+
+    private void addLongPressHandler(View v) {
+        v.setOnLongClickListener(mLongClickListener);
+    }
+
+    private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
+        ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+                new ArrayList<ResourceWallpaperInfo>(24);
+
+        Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+        if (r != null) {
+            try {
+                Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+                bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+
+        // Add an entry for the default wallpaper (stored in system resources)
+        ResourceWallpaperInfo defaultWallpaperInfo = getDefaultWallpaperInfo();
+        if (defaultWallpaperInfo != null) {
+            bundledWallpapers.add(0, defaultWallpaperInfo);
+        }
+        return bundledWallpapers;
+    }
+
+    private ResourceWallpaperInfo getDefaultWallpaperInfo() {
+        Resources sysRes = Resources.getSystem();
+        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+        File defaultThumbFile = new File(getFilesDir(), "default_thumb.jpg");
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            Resources res = getResources();
+            Point defaultThumbSize = getDefaultThumbnailSize(res);
+            int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
+            thumb = createThumbnail(
+                    defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
+            if (thumb != null) {
+                try {
+                    defaultThumbFile.createNewFile();
+                    FileOutputStream thumbFileStream =
+                            openFileOutput(defaultThumbFile.getName(), Context.MODE_PRIVATE);
+                    thumb.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+                    thumbFileStream.close();
+                    defaultWallpaperExists = true;
+                } catch (IOException e) {
+                    Log.e(TAG, "Error while writing default wallpaper thumbnail to file " + e);
+                    defaultThumbFile.delete();
+                }
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
+        }
+        return null;
+    }
+
+    public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
+        // Context.getPackageName() may return the "original" package name,
+        // com.android.launcher3; Resources needs the real package name,
+        // com.android.launcher3. So we ask Resources for what it thinks the
+        // package name should be.
+        final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+        try {
+            ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+            return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    private ArrayList<ResourceWallpaperInfo> addWallpapers(
+            Resources res, String packageName, int listResId) {
+        ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+                new ArrayList<ResourceWallpaperInfo>(24);
+        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));
+                    bundledWallpapers.add(wallpaperInfo);
+                    // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
+                }
+            } else {
+                Log.e(TAG, "Couldn't find wallpaper " + extra);
+            }
+        }
+        return bundledWallpapers;
+    }
+
+    public CropView getCropView() {
+        return mCropView;
+    }
+
+    public SavedWallpaperImages getSavedImages() {
+        return mSavedImages;
+    }
+
+    public void onLiveWallpaperPickerLaunch() {
+        mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
+    }
+
+    static class ZeroPaddingDrawable extends LevelListDrawable {
+        public ZeroPaddingDrawable(Drawable d) {
+            super();
+            addLevel(0, 0, d);
+            setLevel(0);
+        }
+
+        @Override
+        public boolean getPadding(Rect padding) {
+            padding.set(0, 0, 0, 0);
+            return true;
+        }
+    }
+
+    private static class BuiltInWallpapersAdapter extends BaseAdapter implements ListAdapter {
+        private LayoutInflater mLayoutInflater;
+        private ArrayList<ResourceWallpaperInfo> mWallpapers;
+
+        BuiltInWallpapersAdapter(Activity activity, ArrayList<ResourceWallpaperInfo> wallpapers) {
+            mLayoutInflater = activity.getLayoutInflater();
+            mWallpapers = wallpapers;
+        }
+
+        public int getCount() {
+            return mWallpapers.size();
+        }
+
+        public ResourceWallpaperInfo getItem(int position) {
+            return mWallpapers.get(position);
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Drawable thumb = mWallpapers.get(position).mThumb;
+            if (thumb == null) {
+                Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+            }
+            return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
+        }
+    }
+
+    public static View createImageTileView(LayoutInflater layoutInflater, int position,
+            View convertView, ViewGroup parent, Drawable thumb) {
+        View view;
+
+        if (convertView == null) {
+            view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        setWallpaperItemPaddingToZero((FrameLayout) view);
+
+        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+
+        if (thumb != null) {
+            image.setImageDrawable(thumb);
+            thumb.setDither(true);
+        }
+
+        return view;
+    }
+}
diff --git a/src/com/android/launcher3/WallpaperRootView.java b/src/com/android/launcher3/WallpaperRootView.java
new file mode 100644
index 0000000..ceaa043
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperRootView.java
@@ -0,0 +1,39 @@
+/*
+ * 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.launcher3;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+public class WallpaperRootView extends RelativeLayout {
+    private final WallpaperPickerActivity a;
+    public WallpaperRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        a = (WallpaperPickerActivity) context;
+    }
+    public WallpaperRootView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        a = (WallpaperPickerActivity) context;
+    }
+
+    protected boolean fitSystemWindows(Rect insets) {
+        a.setWallpaperStripYOffset(insets.bottom);
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/WeightWatcher.java b/src/com/android/launcher3/WeightWatcher.java
new file mode 100644
index 0000000..70b8afe
--- /dev/null
+++ b/src/com/android/launcher3/WeightWatcher.java
@@ -0,0 +1,269 @@
+/*
+ * 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.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class WeightWatcher extends LinearLayout {
+    private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
+    private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
+    private static final int TEXT_COLOR = 0xFFFFFFFF;
+    private static final int BACKGROUND_COLOR = 0xc0000000;
+
+    private static final int UPDATE_RATE = 5000;
+
+    private static final int MSG_START = 1;
+    private static final int MSG_STOP = 2;
+    private static final int MSG_UPDATE = 3;
+
+    static int indexOf(int[] a, int x) {
+        for (int i=0; i<a.length; i++) {
+            if (a[i] == x) return i;
+        }
+        return -1;
+    }
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_START:
+                    mHandler.sendEmptyMessage(MSG_UPDATE);
+                    break;
+                case MSG_STOP:
+                    mHandler.removeMessages(MSG_UPDATE);
+                    break;
+                case MSG_UPDATE:
+                    int[] pids = mMemoryService.getTrackedProcesses();
+
+                    final int N = getChildCount();
+                    if (pids.length != N) initViews();
+                    else for (int i=0; i<N; i++) {
+                        ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
+                        if (indexOf(pids, pw.getPid()) < 0) {
+                            initViews();
+                            break;
+                        }
+                        pw.update();
+                    }
+                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
+                    break;
+            }
+        }
+    };
+    private MemoryTracker mMemoryService;
+
+    public WeightWatcher(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        ServiceConnection connection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
+                initViews();
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+                mMemoryService = null;
+            }
+        };
+        context.bindService(new Intent(context, MemoryTracker.class),
+                connection, Context.BIND_AUTO_CREATE);
+
+        setOrientation(LinearLayout.VERTICAL);
+
+        setBackgroundColor(BACKGROUND_COLOR);
+    }
+
+    public void initViews() {
+        removeAllViews();
+        int[] processes = mMemoryService.getTrackedProcesses();
+        for (int i=0; i<processes.length; i++) {
+            final ProcessWatcher v = new ProcessWatcher(getContext());
+            v.setPid(processes[i]);
+            addView(v);
+        }
+    }
+
+    public WeightWatcher(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mHandler.sendEmptyMessage(MSG_START);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mHandler.sendEmptyMessage(MSG_STOP);
+    }
+
+    public class ProcessWatcher extends LinearLayout {
+        GraphView mRamGraph;
+        TextView mText;
+        int mPid;
+        private MemoryTracker.ProcessMemInfo mMemInfo;
+
+        public ProcessWatcher(Context context) {
+            this(context, null);
+        }
+
+        public ProcessWatcher(Context context, AttributeSet attrs) {
+            super(context, attrs);
+
+            final float dp = getResources().getDisplayMetrics().density;
+
+            mText = new TextView(getContext());
+            mText.setTextColor(TEXT_COLOR);
+            mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
+            mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+            final int p = (int)(2*dp);
+            setPadding(p, 0, p, 0);
+
+            mRamGraph = new GraphView(getContext());
+
+            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                    0,
+                    (int)(14 * dp),
+                    1f
+            );
+
+            addView(mText, params);
+            params.leftMargin = (int)(4*dp);
+            params.weight = 0f;
+            params.width = (int)(200 * dp);
+            addView(mRamGraph, params);
+        }
+
+        public void setPid(int pid) {
+            mPid = pid;
+            mMemInfo = mMemoryService.getMemInfo(mPid);
+            if (mMemInfo == null) {
+                Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
+                initViews();
+            }
+        }
+
+        public int getPid() {
+            return mPid;
+        }
+
+        public String getUptimeString() {
+            long sec = mMemInfo.getUptime() / 1000;
+            StringBuilder sb = new StringBuilder();
+            long days = sec / 86400;
+            if (days > 0) {
+                sec -= days * 86400;
+                sb.append(days);
+                sb.append("d");
+            }
+
+            long hours = sec / 3600;
+            if (hours > 0) {
+                sec -= hours * 3600;
+                sb.append(hours);
+                sb.append("h");
+            }
+
+            long mins = sec / 60;
+            if (mins > 0) {
+                sec -= mins * 60;
+                sb.append(mins);
+                sb.append("m");
+            }
+
+            sb.append(sec);
+            sb.append("s");
+            return sb.toString();
+        }
+
+        public void update() {
+            //Log.v("WeightWatcher.ProcessWatcher",
+            //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
+            mText.setText("(" + mPid
+                          + (mPid == android.os.Process.myPid()
+                                ? "/A"  // app
+                                : "/S") // service
+                          + ") up " + getUptimeString()
+                          + " P=" + mMemInfo.currentPss
+                          + " U=" + mMemInfo.currentUss
+                          );
+            mRamGraph.invalidate();
+        }
+
+        public class GraphView extends View {
+            Paint pssPaint, ussPaint, headPaint;
+
+            public GraphView(Context context, AttributeSet attrs) {
+                super(context, attrs);
+
+                pssPaint = new Paint();
+                pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
+                ussPaint = new Paint();
+                ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
+                headPaint = new Paint();
+                headPaint.setColor(Color.WHITE);
+            }
+
+            public GraphView(Context context) {
+                this(context, null);
+            }
+
+            @Override
+            public void onDraw(Canvas c) {
+                int w = c.getWidth();
+                int h = c.getHeight();
+
+                if (mMemInfo == null) return;
+
+                final int N = mMemInfo.pss.length;
+                final float barStep = (float) w / N;
+                final float barWidth = Math.max(1, barStep);
+                final float scale = (float) h / mMemInfo.max;
+
+                int i;
+                float x;
+                for (i=0; i<N; i++) {
+                    x = i * barStep;
+                    c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
+                    c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
+                }
+                x = mMemInfo.head * barStep;
+                c.drawRect(x, 0, x + barWidth, h, headPaint);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/WidgetAdder.java b/src/com/android/launcher3/WidgetAdder.java
new file mode 100644
index 0000000..79ac504
--- /dev/null
+++ b/src/com/android/launcher3/WidgetAdder.java
@@ -0,0 +1,7 @@
+package com.android.launcher3;
+
+import android.app.Activity;
+
+public class WidgetAdder extends Activity {
+
+}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
new file mode 100644
index 0000000..07b4f6f
--- /dev/null
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -0,0 +1,626 @@
+package com.android.launcher3;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+abstract class SoftReferenceThreadLocal<T> {
+    private ThreadLocal<SoftReference<T>> mThreadLocal;
+    public SoftReferenceThreadLocal() {
+        mThreadLocal = new ThreadLocal<SoftReference<T>>();
+    }
+
+    abstract T initialValue();
+
+    public void set(T t) {
+        mThreadLocal.set(new SoftReference<T>(t));
+    }
+
+    public T get() {
+        SoftReference<T> reference = mThreadLocal.get();
+        T obj;
+        if (reference == null) {
+            obj = initialValue();
+            mThreadLocal.set(new SoftReference<T>(obj));
+            return obj;
+        } else {
+            obj = reference.get();
+            if (obj == null) {
+                obj = initialValue();
+                mThreadLocal.set(new SoftReference<T>(obj));
+            }
+            return obj;
+        }
+    }
+}
+
+class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
+    @Override
+    protected Canvas initialValue() {
+        return new Canvas();
+    }
+}
+
+class PaintCache extends SoftReferenceThreadLocal<Paint> {
+    @Override
+    protected Paint initialValue() {
+        return null;
+    }
+}
+
+class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
+    @Override
+    protected Bitmap initialValue() {
+        return null;
+    }
+}
+
+class RectCache extends SoftReferenceThreadLocal<Rect> {
+    @Override
+    protected Rect initialValue() {
+        return new Rect();
+    }
+}
+
+class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
+    @Override
+    protected BitmapFactory.Options initialValue() {
+        return new BitmapFactory.Options();
+    }
+}
+
+public class WidgetPreviewLoader {
+    static final String TAG = "WidgetPreviewLoader";
+
+    private int mPreviewBitmapWidth;
+    private int mPreviewBitmapHeight;
+    private String mSize;
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private PagedViewCellLayout mWidgetSpacingLayout;
+
+    // Used for drawing shortcut previews
+    private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
+    private PaintCache mCachedShortcutPreviewPaint = new PaintCache();
+    private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
+
+    // Used for drawing widget previews
+    private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
+    private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
+    private RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
+    private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
+    private String mCachedSelectQuery;
+    private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache();
+
+    private int mAppIconSize;
+    private IconCache mIconCache;
+
+    private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
+
+    private CacheDb mDb;
+
+    private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews;
+    private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
+    private static HashSet<String> sInvalidPackages;
+
+    static {
+        sInvalidPackages = new HashSet<String>();
+    }
+
+    public WidgetPreviewLoader(Context context) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        mContext = context;
+        mPackageManager = mContext.getPackageManager();
+        mAppIconSize = grid.iconSizePx;
+        mIconCache = app.getIconCache();
+        mDb = app.getWidgetPreviewCacheDb();
+        mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
+        mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
+    }
+
+    public void setPreviewSize(int previewWidth, int previewHeight,
+            PagedViewCellLayout widgetSpacingLayout) {
+        mPreviewBitmapWidth = previewWidth;
+        mPreviewBitmapHeight = previewHeight;
+        mSize = previewWidth + "x" + previewHeight;
+        mWidgetSpacingLayout = widgetSpacingLayout;
+    }
+
+    public Bitmap getPreview(final Object o) {
+        final String name = getObjectName(o);
+        final String packageName = getObjectPackage(o);
+        // check if the package is valid
+        boolean packageValid = true;
+        synchronized(sInvalidPackages) {
+            packageValid = !sInvalidPackages.contains(packageName);
+        }
+        if (!packageValid) {
+            return null;
+        }
+        if (packageValid) {
+            synchronized(mLoadedPreviews) {
+                // check if it exists in our existing cache
+                if (mLoadedPreviews.containsKey(name) && mLoadedPreviews.get(name).get() != null) {
+                    return mLoadedPreviews.get(name).get();
+                }
+            }
+        }
+
+        Bitmap unusedBitmap = null;
+        synchronized(mUnusedBitmaps) {
+            // not in cache; we need to load it from the db
+            while ((unusedBitmap == null || !unusedBitmap.isMutable() ||
+                    unusedBitmap.getWidth() != mPreviewBitmapWidth ||
+                    unusedBitmap.getHeight() != mPreviewBitmapHeight)
+                    && mUnusedBitmaps.size() > 0) {
+                unusedBitmap = mUnusedBitmaps.remove(0).get();
+            }
+            if (unusedBitmap != null) {
+                final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+                c.setBitmap(unusedBitmap);
+                c.drawColor(0, PorterDuff.Mode.CLEAR);
+                c.setBitmap(null);
+            }
+        }
+
+        if (unusedBitmap == null) {
+            unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
+                    Bitmap.Config.ARGB_8888);
+        }
+
+        Bitmap preview = null;
+
+        if (packageValid) {
+            preview = readFromDb(name, unusedBitmap);
+        }
+
+        if (preview != null) {
+            synchronized(mLoadedPreviews) {
+                mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
+            }
+            return preview;
+        } else {
+            // it's not in the db... we need to generate it
+            final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
+            preview = generatedPreview;
+            if (preview != unusedBitmap) {
+                throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
+            }
+
+            synchronized(mLoadedPreviews) {
+                mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
+            }
+
+            // write to db on a thread pool... this can be done lazily and improves the performance
+            // of the first time widget previews are loaded
+            new AsyncTask<Void, Void, Void>() {
+                public Void doInBackground(Void ... args) {
+                    writeToDb(o, generatedPreview);
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+
+            return preview;
+        }
+    }
+
+    public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
+        String name = getObjectName(o);
+        synchronized (mLoadedPreviews) {
+            if (mLoadedPreviews.containsKey(name)) {
+                Bitmap b = mLoadedPreviews.get(name).get();
+                if (b == bitmapToRecycle) {
+                    mLoadedPreviews.remove(name);
+                    if (bitmapToRecycle.isMutable()) {
+                        synchronized (mUnusedBitmaps) {
+                            mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
+                        }
+                    }
+                } else {
+                    throw new RuntimeException("Bitmap passed in doesn't match up");
+                }
+            }
+        }
+    }
+
+    static class CacheDb extends SQLiteOpenHelper {
+        final static int DB_VERSION = 2;
+        final static String DB_NAME = "widgetpreviews.db";
+        final static String TABLE_NAME = "shortcut_and_widget_previews";
+        final static String COLUMN_NAME = "name";
+        final static String COLUMN_SIZE = "size";
+        final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
+        Context mContext;
+
+        public CacheDb(Context context) {
+            super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
+            // Store the context for later use
+            mContext = context;
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+                    COLUMN_NAME + " TEXT NOT NULL, " +
+                    COLUMN_SIZE + " TEXT NOT NULL, " +
+                    COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
+                    "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
+                    ");");
+        }
+
+        @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);
+            }
+        }
+    }
+
+    private static final String WIDGET_PREFIX = "Widget:";
+    private static final String SHORTCUT_PREFIX = "Shortcut:";
+
+    private static String getObjectName(Object o) {
+        // should cache the string builder
+        StringBuilder sb = new StringBuilder();
+        String output;
+        if (o instanceof AppWidgetProviderInfo) {
+            sb.append(WIDGET_PREFIX);
+            sb.append(((AppWidgetProviderInfo) o).provider.flattenToString());
+            output = sb.toString();
+            sb.setLength(0);
+        } else {
+            sb.append(SHORTCUT_PREFIX);
+
+            ResolveInfo info = (ResolveInfo) o;
+            sb.append(new ComponentName(info.activityInfo.packageName,
+                    info.activityInfo.name).flattenToString());
+            output = sb.toString();
+            sb.setLength(0);
+        }
+        return output;
+    }
+
+    private String getObjectPackage(Object o) {
+        if (o instanceof AppWidgetProviderInfo) {
+            return ((AppWidgetProviderInfo) o).provider.getPackageName();
+        } else {
+            ResolveInfo info = (ResolveInfo) o;
+            return info.activityInfo.packageName;
+        }
+    }
+
+    private void writeToDb(Object o, Bitmap preview) {
+        String name = getObjectName(o);
+        SQLiteDatabase db = mDb.getWritableDatabase();
+        ContentValues values = new ContentValues();
+
+        values.put(CacheDb.COLUMN_NAME, name);
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
+        values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
+        values.put(CacheDb.COLUMN_SIZE, mSize);
+        db.insert(CacheDb.TABLE_NAME, null, values);
+    }
+
+    public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
+        synchronized(sInvalidPackages) {
+            sInvalidPackages.add(packageName);
+        }
+        new AsyncTask<Void, Void, Void>() {
+            public Void doInBackground(Void ... args) {
+                SQLiteDatabase db = cacheDb.getWritableDatabase();
+                db.delete(CacheDb.TABLE_NAME,
+                        CacheDb.COLUMN_NAME + " LIKE ? OR " +
+                        CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
+                        new String[] {
+                            WIDGET_PREFIX + packageName + "/%",
+                            SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query
+                            );
+                synchronized(sInvalidPackages) {
+                    sInvalidPackages.remove(packageName);
+                }
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+    }
+
+    public static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
+        new AsyncTask<Void, Void, Void>() {
+            public Void doInBackground(Void ... args) {
+                SQLiteDatabase db = cacheDb.getWritableDatabase();
+                db.delete(CacheDb.TABLE_NAME,
+                        CacheDb.COLUMN_NAME + " = ? ", // SELECT query
+                        new String[] { objectName }); // args to SELECT query
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+    }
+
+    private Bitmap readFromDb(String name, Bitmap b) {
+        if (mCachedSelectQuery == null) {
+            mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
+                    CacheDb.COLUMN_SIZE + " = ?";
+        }
+        SQLiteDatabase db = mDb.getReadableDatabase();
+        Cursor result = db.query(CacheDb.TABLE_NAME,
+                new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
+                mCachedSelectQuery, // select query
+                new String[] { name, mSize }, // args to select query
+                null,
+                null,
+                null,
+                null);
+        if (result.getCount() > 0) {
+            result.moveToFirst();
+            byte[] blob = result.getBlob(0);
+            result.close();
+            final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get();
+            opts.inBitmap = b;
+            opts.inSampleSize = 1;
+            try {
+                return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+            } catch (IllegalArgumentException e) {
+                removeItemFromDb(mDb, name);
+                return null;
+            }
+        } else {
+            result.close();
+            return null;
+        }
+    }
+
+    public Bitmap generatePreview(Object info, Bitmap preview) {
+        if (preview != null &&
+                (preview.getWidth() != mPreviewBitmapWidth ||
+                preview.getHeight() != mPreviewBitmapHeight)) {
+            throw new RuntimeException("Improperly sized bitmap passed as argument");
+        }
+        if (info instanceof AppWidgetProviderInfo) {
+            return generateWidgetPreview((AppWidgetProviderInfo) info, preview);
+        } else {
+            return generateShortcutPreview(
+                    (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
+        }
+    }
+
+    public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) {
+        int[] cellSpans = Launcher.getSpanForWidget(mContext, info);
+        int maxWidth = maxWidthForWidgetPreview(cellSpans[0]);
+        int maxHeight = maxHeightForWidgetPreview(cellSpans[1]);
+        return generateWidgetPreview(info.provider, info.previewImage, info.icon,
+                cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null);
+    }
+
+    public int maxWidthForWidgetPreview(int spanX) {
+        return Math.min(mPreviewBitmapWidth,
+                mWidgetSpacingLayout.estimateCellWidth(spanX));
+    }
+
+    public int maxHeightForWidgetPreview(int spanY) {
+        return Math.min(mPreviewBitmapHeight,
+                mWidgetSpacingLayout.estimateCellHeight(spanY));
+    }
+
+    public Bitmap generateWidgetPreview(ComponentName provider, int previewImage,
+            int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight,
+            Bitmap preview, int[] preScaledWidthOut) {
+        // Load the preview image if possible
+        String packageName = provider.getPackageName();
+        if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
+        if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
+
+        Drawable drawable = null;
+        if (previewImage != 0) {
+            drawable = mPackageManager.getDrawable(packageName, previewImage, null);
+            if (drawable == null) {
+                Log.w(TAG, "Can't load widget preview drawable 0x" +
+                        Integer.toHexString(previewImage) + " for provider: " + provider);
+            }
+        }
+
+        int previewWidth;
+        int previewHeight;
+        Bitmap defaultPreview = null;
+        boolean widgetPreviewExists = (drawable != null);
+        if (widgetPreviewExists) {
+            previewWidth = drawable.getIntrinsicWidth();
+            previewHeight = drawable.getIntrinsicHeight();
+        } else {
+            // Generate a preview image if we couldn't load one
+            if (cellHSpan < 1) cellHSpan = 1;
+            if (cellVSpan < 1) cellVSpan = 1;
+
+            BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
+                    .getDrawable(R.drawable.widget_tile);
+            final int previewDrawableWidth = previewDrawable
+                    .getIntrinsicWidth();
+            final int previewDrawableHeight = previewDrawable
+                    .getIntrinsicHeight();
+            previewWidth = previewDrawableWidth * cellHSpan;
+            previewHeight = previewDrawableHeight * cellVSpan;
+
+            defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight,
+                    Config.ARGB_8888);
+            final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+            c.setBitmap(defaultPreview);
+            previewDrawable.setBounds(0, 0, previewWidth, previewHeight);
+            previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
+                    Shader.TileMode.REPEAT);
+            previewDrawable.draw(c);
+            c.setBitmap(null);
+
+            // Draw the icon in the top left corner
+            int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
+            int smallestSide = Math.min(previewWidth, previewHeight);
+            float iconScale = Math.min((float) smallestSide
+                    / (mAppIconSize + 2 * minOffset), 1f);
+
+            try {
+                Drawable icon = null;
+                int hoffset =
+                        (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
+                int yoffset =
+                        (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
+                if (iconId > 0)
+                    icon = mIconCache.getFullResIcon(packageName, iconId);
+                if (icon != null) {
+                    renderDrawableToBitmap(icon, defaultPreview, hoffset,
+                            yoffset, (int) (mAppIconSize * iconScale),
+                            (int) (mAppIconSize * iconScale));
+                }
+            } catch (Resources.NotFoundException e) {
+            }
+        }
+
+        // Scale to fit width only - let the widget preview be clipped in the
+        // vertical dimension
+        float scale = 1f;
+        if (preScaledWidthOut != null) {
+            preScaledWidthOut[0] = previewWidth;
+        }
+        if (previewWidth > maxPreviewWidth) {
+            scale = maxPreviewWidth / (float) previewWidth;
+        }
+        if (scale != 1f) {
+            previewWidth = (int) (scale * previewWidth);
+            previewHeight = (int) (scale * previewHeight);
+        }
+
+        // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
+        if (preview == null) {
+            preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
+        }
+
+        // Draw the scaled preview into the final bitmap
+        int x = (preview.getWidth() - previewWidth) / 2;
+        if (widgetPreviewExists) {
+            renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,
+                    previewHeight);
+        } else {
+            final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+            final Rect src = mCachedAppWidgetPreviewSrcRect.get();
+            final Rect dest = mCachedAppWidgetPreviewDestRect.get();
+            c.setBitmap(preview);
+            src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
+            dest.set(x, 0, x + previewWidth, previewHeight);
+
+            Paint p = mCachedAppWidgetPreviewPaint.get();
+            if (p == null) {
+                p = new Paint();
+                p.setFilterBitmap(true);
+                mCachedAppWidgetPreviewPaint.set(p);
+            }
+            c.drawBitmap(defaultPreview, src, dest, p);
+            c.setBitmap(null);
+        }
+        return preview;
+    }
+
+    private Bitmap generateShortcutPreview(
+            ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) {
+        Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
+        final Canvas c = mCachedShortcutPreviewCanvas.get();
+        if (tempBitmap == null ||
+                tempBitmap.getWidth() != maxWidth ||
+                tempBitmap.getHeight() != maxHeight) {
+            tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+            mCachedShortcutPreviewBitmap.set(tempBitmap);
+        } else {
+            c.setBitmap(tempBitmap);
+            c.drawColor(0, PorterDuff.Mode.CLEAR);
+            c.setBitmap(null);
+        }
+        // Render the icon
+        Drawable icon = mIconCache.getFullResIcon(info);
+
+        int paddingTop = mContext.
+                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
+        int paddingLeft = mContext.
+                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
+        int paddingRight = mContext.
+                getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
+
+        int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
+
+        renderDrawableToBitmap(
+                icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
+
+        if (preview != null &&
+                (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) {
+            throw new RuntimeException("Improperly sized bitmap passed as argument");
+        } else if (preview == null) {
+            preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+        }
+
+        c.setBitmap(preview);
+        // Draw a desaturated/scaled version of the icon in the background as a watermark
+        Paint p = mCachedShortcutPreviewPaint.get();
+        if (p == null) {
+            p = new Paint();
+            ColorMatrix colorMatrix = new ColorMatrix();
+            colorMatrix.setSaturation(0);
+            p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+            p.setAlpha((int) (255 * 0.06f));
+            mCachedShortcutPreviewPaint.set(p);
+        }
+        c.drawBitmap(tempBitmap, 0, 0, p);
+        c.setBitmap(null);
+
+        renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
+
+        return preview;
+    }
+
+
+    public static void renderDrawableToBitmap(
+            Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
+        renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
+    }
+
+    private static void renderDrawableToBitmap(
+            Drawable d, Bitmap bitmap, int x, int y, int w, int h,
+            float scale) {
+        if (bitmap != null) {
+            Canvas c = new Canvas(bitmap);
+            c.scale(scale, scale);
+            Rect oldBounds = d.copyBounds();
+            d.setBounds(x, y, x + w, y + h);
+            d.draw(c);
+            d.setBounds(oldBounds); // Restore the bounds
+            c.setBitmap(null);
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
new file mode 100644
index 0000000..1d5b5ac
--- /dev/null
+++ b/src/com/android/launcher3/Workspace.java
@@ -0,0 +1,4477 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.WallpaperManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+
+import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.Launcher.CustomContentCallbacks;
+import com.android.launcher3.LauncherSettings.Favorites;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * The workspace is a wide area with a wallpaper and a finite number of pages.
+ * Each page contains a number of icons, folders or widgets the user can
+ * interact with. A workspace is meant to be used with a fixed width only.
+ */
+public class Workspace extends SmoothPagedView
+        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
+        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+        Insettable {
+    private static final String TAG = "Launcher.Workspace";
+
+    // Y rotation to apply to the workspace screens
+    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
+
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
+    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
+
+    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
+    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
+    private static final int FLING_THRESHOLD_VELOCITY = 500;
+
+    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+
+    // These animators are used to fade the children's outlines
+    private ObjectAnimator mChildrenOutlineFadeInAnimation;
+    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
+    private float mChildrenOutlineAlpha = 0;
+
+    // These properties refer to the background protection gradient used for AllApps and Customize
+    private ValueAnimator mBackgroundFadeInAnimation;
+    private ValueAnimator mBackgroundFadeOutAnimation;
+    private Drawable mBackground;
+    boolean mDrawBackground = true;
+    private float mBackgroundAlpha = 0;
+
+    private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
+    private long mTouchDownTime = -1;
+    private long mCustomContentShowTime = -1;
+
+    private LayoutTransition mLayoutTransition;
+    private final WallpaperManager mWallpaperManager;
+    private IBinder mWindowToken;
+
+    private int mOriginalDefaultPage;
+    private int mDefaultPage;
+
+    private ShortcutAndWidgetContainer mDragSourceInternal;
+    private static boolean sAccessibilityEnabled;
+
+    // The screen id used for the empty screen always present to the right.
+    private final static long EXTRA_EMPTY_SCREEN_ID = -201;
+    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
+
+    private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
+    private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
+
+    /**
+     * CellInfo for the cell that is currently being dragged
+     */
+    private CellLayout.CellInfo mDragInfo;
+
+    /**
+     * Target drop area calculated during last acceptDrop call.
+     */
+    private int[] mTargetCell = new int[2];
+    private int mDragOverX = -1;
+    private int mDragOverY = -1;
+
+    static Rect mLandscapeCellLayoutMetrics = null;
+    static Rect mPortraitCellLayoutMetrics = null;
+
+    CustomContentCallbacks mCustomContentCallbacks;
+    boolean mCustomContentShowing;
+    private float mLastCustomContentScrollProgress = -1f;
+    private String mCustomContentDescription = "";
+
+    /**
+     * The CellLayout that is currently being dragged over
+     */
+    private CellLayout mDragTargetLayout = null;
+    /**
+     * The CellLayout that we will show as glowing
+     */
+    private CellLayout mDragOverlappingLayout = null;
+
+    /**
+     * The CellLayout which will be dropped to
+     */
+    private CellLayout mDropToLayout = null;
+
+    private Launcher mLauncher;
+    private IconCache mIconCache;
+    private DragController mDragController;
+
+    // These are temporary variables to prevent having to allocate a new object just to
+    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+    private int[] mTempCell = new int[2];
+    private int[] mTempPt = new int[2];
+    private int[] mTempEstimate = new int[2];
+    private float[] mDragViewVisualCenter = new float[2];
+    private float[] mTempCellLayoutCenterCoordinates = new float[2];
+    private Matrix mTempInverseMatrix = new Matrix();
+
+    private SpringLoadedDragController mSpringLoadedDragController;
+    private float mSpringLoadedShrinkFactor;
+    private float mOverviewModeShrinkFactor;
+    private int mOverviewModePageOffset;
+
+    // State variable that indicates whether the pages are small (ie when you're
+    // in all apps or customize mode)
+
+    enum State { NORMAL, SPRING_LOADED, SMALL, OVERVIEW};
+    private State mState = State.NORMAL;
+    private boolean mIsSwitchingState = false;
+
+    boolean mAnimatingViewIntoPlace = false;
+    boolean mIsDragOccuring = false;
+    boolean mChildrenLayersEnabled = true;
+
+    private boolean mStripScreensOnPageStopMoving = false;
+
+    /** Is the user is dragging an item near the edge of a page? */
+    private boolean mInScrollArea = false;
+
+    private HolographicOutlineHelper mOutlineHelper;
+    private Bitmap mDragOutline = null;
+    private final Rect mTempRect = new Rect();
+    private final int[] mTempXY = new int[2];
+    private int[] mTempVisiblePagesRange = new int[2];
+    private boolean mOverscrollTransformsSet;
+    private float mLastOverscrollPivotX;
+    public static final int DRAG_BITMAP_PADDING = 2;
+    private boolean mWorkspaceFadeInAdjacentScreens;
+
+    WallpaperOffsetInterpolator mWallpaperOffset;
+    private Runnable mDelayedResizeRunnable;
+    private Runnable mDelayedSnapToPageRunnable;
+    private Point mDisplaySize = new Point();
+    private int mCameraDistance;
+
+    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
+    private static final int FOLDER_CREATION_TIMEOUT = 0;
+    private static final int REORDER_TIMEOUT = 250;
+    private final Alarm mFolderCreationAlarm = new Alarm();
+    private final Alarm mReorderAlarm = new Alarm();
+    private FolderRingAnimator mDragFolderRingAnimator = null;
+    private FolderIcon mDragOverFolderIcon = null;
+    private boolean mCreateUserFolderOnDrop = false;
+    private boolean mAddToExistingFolderOnDrop = false;
+    private DropTarget.DragEnforcer mDragEnforcer;
+    private float mMaxDistanceForFolderCreation;
+
+    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
+    private float mXDown;
+    private float mYDown;
+    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
+
+    // Relating to the animation of items being dropped externally
+    public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
+    public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
+    public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
+    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
+    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
+
+    // Related to dragging, folder creation and reordering
+    private static final int DRAG_MODE_NONE = 0;
+    private static final int DRAG_MODE_CREATE_FOLDER = 1;
+    private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
+    private static final int DRAG_MODE_REORDER = 3;
+    private int mDragMode = DRAG_MODE_NONE;
+    private int mLastReorderX = -1;
+    private int mLastReorderY = -1;
+
+    private SparseArray<Parcelable> mSavedStates;
+    private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
+
+    // These variables are used for storing the initial and final values during workspace animations
+    private int mSavedScrollX;
+    private float mSavedRotationY;
+    private float mSavedTranslationX;
+
+    private float mCurrentScale;
+    private float mNewScale;
+    private float[] mOldBackgroundAlphas;
+    private float[] mOldAlphas;
+    private float[] mNewBackgroundAlphas;
+    private float[] mNewAlphas;
+    private int mLastChildCount = -1;
+    private float mTransitionProgress;
+
+    private Runnable mDeferredAction;
+    private boolean mDeferDropAfterUninstall;
+    private boolean mUninstallSuccessful;
+
+    private final Runnable mBindPages = new Runnable() {
+        @Override
+        public void run() {
+            mLauncher.getModel().bindRemainingSynchronousPages();
+        }
+    };
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attributes set containing the Workspace's customization values.
+     */
+    public Workspace(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attributes set containing the Workspace's customization values.
+     * @param defStyle Unused.
+     */
+    public Workspace(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mContentIsRefreshable = false;
+
+        mOutlineHelper = HolographicOutlineHelper.obtain(context);
+
+        mDragEnforcer = new DropTarget.DragEnforcer(context);
+        // With workspace, data is available straight from the get-go
+        setDataIsReady();
+
+        mLauncher = (Launcher) context;
+        final Resources res = getResources();
+        mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
+        mFadeInAdjacentScreens = false;
+        mWallpaperManager = WallpaperManager.getInstance(context);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.Workspace, defStyle, 0);
+        mSpringLoadedShrinkFactor =
+            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+        mOverviewModeShrinkFactor =
+                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100.0f;
+        mOverviewModePageOffset = res.getDimensionPixelSize(R.dimen.overview_mode_page_offset);
+        mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
+        mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
+        a.recycle();
+
+        setOnHierarchyChangeListener(this);
+        setHapticFeedbackEnabled(false);
+
+        initWorkspace();
+
+        // Disable multitouch across the workspace/all apps/customize tray
+        setMotionEventSplittingEnabled(true);
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+    }
+
+    // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
+    // dimension if unsuccessful
+    public int[] estimateItemSize(int hSpan, int vSpan,
+            ItemInfo itemInfo, boolean springLoaded) {
+        int[] size = new int[2];
+        if (getChildCount() > 0) {
+            // Use the first non-custom page to estimate the child position
+            CellLayout cl = (CellLayout) getChildAt(numCustomPages());
+            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+            size[0] = r.width();
+            size[1] = r.height();
+            if (springLoaded) {
+                size[0] *= mSpringLoadedShrinkFactor;
+                size[1] *= mSpringLoadedShrinkFactor;
+            }
+            return size;
+        } else {
+            size[0] = Integer.MAX_VALUE;
+            size[1] = Integer.MAX_VALUE;
+            return size;
+        }
+    }
+
+    public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
+            int hCell, int vCell, int hSpan, int vSpan) {
+        Rect r = new Rect();
+        cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
+        return r;
+    }
+
+    public void onDragStart(final DragSource source, Object info, int dragAction) {
+        mIsDragOccuring = true;
+        updateChildrenLayersEnabled(false);
+        mLauncher.lockScreenOrientation();
+        mLauncher.onInteractionBegin();
+        setChildrenBackgroundAlphaMultipliers(1f);
+        // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
+        InstallShortcutReceiver.enableInstallQueue();
+        UninstallShortcutReceiver.enableUninstallQueue();
+        post(new Runnable() {
+            @Override
+            public void run() {
+                if (mIsDragOccuring) {
+                    addExtraEmptyScreenOnDrag();
+                }
+            }
+        });
+    }
+
+    public void onDragEnd() {
+        mIsDragOccuring = false;
+        updateChildrenLayersEnabled(false);
+        mLauncher.unlockScreenOrientation(false);
+
+        // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+        InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+        UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
+
+        removeExtraEmptyScreen();
+        mDragSourceInternal = null;
+        mLauncher.onInteractionEnd();
+    }
+
+    /**
+     * Initializes various states for this workspace.
+     */
+    protected void initWorkspace() {
+        Context context = getContext();
+        mCurrentPage = mDefaultPage;
+        Launcher.setScreen(mCurrentPage);
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        mIconCache = app.getIconCache();
+        setWillNotDraw(false);
+        setClipChildren(false);
+        setClipToPadding(false);
+        setChildrenDrawnWithCacheEnabled(true);
+
+        // This is a bit of a hack to account for the fact that we translate the workspace
+        // up a bit, and still need to draw the background covering the whole screen.
+        setMinScale(mOverviewModeShrinkFactor - 0.2f);
+        setupLayoutTransition();
+
+        final Resources res = getResources();
+        try {
+            mBackground = res.getDrawable(R.drawable.apps_customize_bg);
+        } catch (Resources.NotFoundException e) {
+            // In this case, we will skip drawing background protection
+        }
+
+        mWallpaperOffset = new WallpaperOffsetInterpolator();
+        Display display = mLauncher.getWindowManager().getDefaultDisplay();
+        display.getSize(mDisplaySize);
+
+        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+    }
+
+    private void setupLayoutTransition() {
+        // We want to show layout transitions when pages are deleted, to close the gap.
+        mLayoutTransition = new LayoutTransition();
+        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        setLayoutTransition(mLayoutTransition);
+    }
+
+    void enableLayoutTransitions() {
+        setLayoutTransition(mLayoutTransition);
+    }
+    void disableLayoutTransitions() {
+        setLayoutTransition(null);
+    }
+
+    @Override
+    protected int getScrollMode() {
+        return SmoothPagedView.X_LARGE_MODE;
+    }
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        if (!(child instanceof CellLayout)) {
+            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
+        }
+        CellLayout cl = ((CellLayout) child);
+        cl.setOnInterceptTouchListener(this);
+        cl.setClickable(true);
+        cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        super.onChildViewAdded(parent, child);
+    }
+
+    protected boolean shouldDrawChild(View child) {
+        final CellLayout cl = (CellLayout) child;
+        return super.shouldDrawChild(child) &&
+            (mIsSwitchingState ||
+             cl.getShortcutsAndWidgets().getAlpha() > 0 ||
+             cl.getBackgroundAlpha() > 0);
+    }
+
+    /**
+     * @return The open folder on the current screen, or null if there is none
+     */
+    Folder getOpenFolder() {
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        int count = dragLayer.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof Folder) {
+                Folder folder = (Folder) child;
+                if (folder.getInfo().opened)
+                    return folder;
+            }
+        }
+        return null;
+    }
+
+    boolean isTouchActive() {
+        return mTouchState != TOUCH_STATE_REST;
+    }
+
+    public void removeAllWorkspaceScreens() {
+        // Disable all layout transitions before removing all pages to ensure that we don't get the
+        // transition animations competing with us changing the scroll when we add pages or the
+        // custom content screen
+        disableLayoutTransitions();
+
+        // Since we increment the current page when we call addCustomContentPage via bindScreens
+        // (and other places), we need to adjust the current page back when we clear the pages
+        if (hasCustomContent()) {
+            removeCustomContentPage();
+        }
+
+        // Remove the pages and clear the screen models
+        removeAllViews();
+        mScreenOrder.clear();
+        mWorkspaceScreens.clear();
+
+        // Re-enable the layout transitions
+        enableLayoutTransitions();
+    }
+
+    public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
+        // Find the index to insert this view into.  If the empty screen exists, then
+        // insert it before that.
+        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
+        if (insertIndex < 0) {
+            insertIndex = mScreenOrder.size();
+        }
+        return insertNewWorkspaceScreen(screenId, insertIndex);
+    }
+
+    public long insertNewWorkspaceScreen(long screenId) {
+        return insertNewWorkspaceScreen(screenId, getChildCount());
+    }
+
+    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
+        if (mWorkspaceScreens.containsKey(screenId)) {
+            throw new RuntimeException("Screen id " + screenId + " already exists!");
+        }
+
+        CellLayout newScreen = (CellLayout)
+                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
+
+        newScreen.setOnLongClickListener(mLongClickListener);
+        newScreen.setOnClickListener(mLauncher);
+        newScreen.setSoundEffectsEnabled(false);
+        mWorkspaceScreens.put(screenId, newScreen);
+        mScreenOrder.add(insertIndex, screenId);
+        addView(newScreen, insertIndex);
+        return screenId;
+    }
+
+    public void createCustomContentPage() {
+        CellLayout customScreen = (CellLayout)
+                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
+
+        mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
+        mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
+
+        // We want no padding on the custom content
+        customScreen.setPadding(0, 0, 0, 0);
+
+        addFullScreenPage(customScreen);
+
+        // Ensure that the current page and default page are maintained.
+        mDefaultPage = mOriginalDefaultPage + 1;
+
+        // Update the custom content hint
+        mLauncher.updateCustomContentHintVisibility();
+        if (mRestorePage != INVALID_RESTORE_PAGE) {
+            mRestorePage = mRestorePage + 1;
+        } else {
+            setCurrentPage(getCurrentPage() + 1);
+        }
+    }
+
+    public void removeCustomContentPage() {
+        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+        if (customScreen == null) {
+            throw new RuntimeException("Expected custom content screen to exist");
+        }
+
+        mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
+        mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
+        removeView(customScreen);
+
+        if (mCustomContentCallbacks != null) {
+            mCustomContentCallbacks.onScrollProgressChanged(0);
+            mCustomContentCallbacks.onHide();
+        }
+
+        mCustomContentCallbacks = null;
+
+        // Ensure that the current page and default page are maintained.
+        mDefaultPage = mOriginalDefaultPage - 1;
+
+        // Update the custom content hint
+        mLauncher.updateCustomContentHintVisibility();
+        if (mRestorePage != INVALID_RESTORE_PAGE) {
+            mRestorePage = mRestorePage - 1;
+        } else {
+            setCurrentPage(getCurrentPage() - 1);
+        }
+    }
+
+    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
+            String description) {
+        if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
+            throw new RuntimeException("Expected custom content screen to exist");
+        }
+
+        // Add the custom content to the full screen custom page
+        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+        int spanX = customScreen.getCountX();
+        int spanY = customScreen.getCountY();
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
+        lp.canReorder  = false;
+        lp.isFullscreen = true;
+        if (customContent instanceof Insettable) {
+            ((Insettable)customContent).setInsets(mInsets);
+        }
+        customScreen.removeAllViews();
+        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
+        mCustomContentDescription = description;
+
+        mCustomContentCallbacks = callbacks;
+    }
+
+    public void addExtraEmptyScreenOnDrag() {
+        boolean lastChildOnScreen = false;
+        boolean childOnFinalScreen = false;
+
+        if (mDragSourceInternal != null) {
+            if (mDragSourceInternal.getChildCount() == 1) {
+                lastChildOnScreen = true;
+            }
+            CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
+            if (indexOfChild(cl) == getChildCount() - 1) {
+                childOnFinalScreen = true;
+            }
+        }
+
+        // If this is the last item on the final screen
+        if (lastChildOnScreen && childOnFinalScreen) {
+            return;
+        }
+        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
+        }
+    }
+
+    public boolean addExtraEmptyScreen() {
+        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
+            return true;
+        }
+        return false;
+    }
+
+    public void removeExtraEmptyScreen() {
+        if (hasExtraEmptyScreen()) {
+            CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+            mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+            mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+            removeView(cl);
+        }
+    }
+
+    public boolean hasExtraEmptyScreen() {
+        int nScreens = getChildCount();
+        nScreens = nScreens - numCustomPages();
+        return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
+    }
+
+    public long commitExtraEmptyScreen() {
+        int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
+        CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+        mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+        mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+
+        long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+        mWorkspaceScreens.put(newId, cl);
+        mScreenOrder.add(newId);
+
+        // Update the page indicator marker
+        if (getPageIndicator() != null) {
+            getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
+        }
+
+        // Update the model for the new screen
+        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+
+        return newId;
+    }
+
+    public CellLayout getScreenWithId(long screenId) {
+        CellLayout layout = mWorkspaceScreens.get(screenId);
+        return layout;
+    }
+
+    public long getIdForScreen(CellLayout layout) {
+        Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
+        while (iter.hasNext()) {
+            long id = iter.next();
+            if (mWorkspaceScreens.get(id) == layout) {
+                return id;
+            }
+        }
+        return -1;
+    }
+
+    public int getPageIndexForScreenId(long screenId) {
+        return indexOfChild(mWorkspaceScreens.get(screenId));
+    }
+
+    public long getScreenIdForPageIndex(int index) {
+        if (0 <= index && index < mScreenOrder.size()) {
+            return mScreenOrder.get(index);
+        }
+        return -1;
+    }
+
+    ArrayList<Long> getScreenOrder() {
+        return mScreenOrder;
+    }
+
+    public void stripEmptyScreens() {
+        if (isPageMoving()) {
+            mStripScreensOnPageStopMoving = true;
+            return;
+        }
+
+        int currentPage = getNextPage();
+        ArrayList<Long> removeScreens = new ArrayList<Long>();
+        for (Long id: mWorkspaceScreens.keySet()) {
+            CellLayout cl = mWorkspaceScreens.get(id);
+            if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
+                removeScreens.add(id);
+            }
+        }
+
+        // We enforce at least one page to add new items to. In the case that we remove the last
+        // such screen, we convert the last screen to the empty screen
+        int minScreens = 1 + numCustomPages();
+
+        int pageShift = 0;
+        for (Long id: removeScreens) {
+            CellLayout cl = mWorkspaceScreens.get(id);
+            mWorkspaceScreens.remove(id);
+            mScreenOrder.remove(id);
+
+            if (getChildCount() > minScreens) {
+                if (indexOfChild(cl) < currentPage) {
+                    pageShift++;
+                }
+                removeView(cl);
+            } else {
+                // if this is the last non-custom content screen, convert it to the empty screen
+                mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
+                mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+            }
+        }
+
+        if (!removeScreens.isEmpty()) {
+            // Update the model if we have changed any screens
+            mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+        }
+
+        if (pageShift >= 0) {
+            setCurrentPage(currentPage - pageShift);
+        }
+    }
+
+    // See implementation for parameter definition.
+    void addInScreen(View child, long container, long screenId,
+            int x, int y, int spanX, int spanY) {
+        addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
+    }
+
+    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+    // See implementation for parameter definition.
+    void addInScreenFromBind(View child, long container, long screenId, int x, int y,
+            int spanX, int spanY) {
+        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
+    }
+
+    // See implementation for parameter definition.
+    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
+            boolean insert) {
+        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
+    }
+
+    /**
+     * Adds the specified child in the specified screen. The position and dimension of
+     * the child are defined by x, y, spanX and spanY.
+     *
+     * @param child The child to add in one of the workspace's screens.
+     * @param screenId The screen in which to add the child.
+     * @param x The X position of the child in the screen's grid.
+     * @param y The Y position of the child in the screen's grid.
+     * @param spanX The number of cells spanned horizontally by the child.
+     * @param spanY The number of cells spanned vertically by the child.
+     * @param insert When true, the child is inserted at the beginning of the children list.
+     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
+     *                          the x and y position in which to place hotseat items. Otherwise
+     *                          we use the x and y position to compute the rank.
+     */
+    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
+            boolean insert, boolean computeXYFromRank) {
+        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            if (getScreenWithId(screenId) == null) {
+                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
+                // DEBUGGING - Print out the stack trace to see where we are adding from
+                new Throwable().printStackTrace();
+                return;
+            }
+        }
+        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+            // This should never happen
+            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+        }
+
+        final CellLayout layout;
+        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            layout = mLauncher.getHotseat().getLayout();
+            child.setOnKeyListener(null);
+
+            // Hide folder title in the hotseat
+            if (child instanceof FolderIcon) {
+                ((FolderIcon) child).setTextVisible(false);
+            }
+
+            if (computeXYFromRank) {
+                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
+                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
+            } else {
+                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
+            }
+        } else {
+            // Show folder title if not in the hotseat
+            if (child instanceof FolderIcon) {
+                ((FolderIcon) child).setTextVisible(true);
+            }
+            layout = getScreenWithId(screenId);
+            child.setOnKeyListener(new IconKeyEventListener());
+        }
+
+        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
+        CellLayout.LayoutParams lp;
+        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
+            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+        } else {
+            lp = (CellLayout.LayoutParams) genericLp;
+            lp.cellX = x;
+            lp.cellY = y;
+            lp.cellHSpan = spanX;
+            lp.cellVSpan = spanY;
+        }
+
+        if (spanX < 0 && spanY < 0) {
+            lp.isLockedToGrid = false;
+        }
+
+        // Get the canonical child id to uniquely represent this view in this screen
+        int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
+        boolean markCellsAsOccupied = !(child instanceof Folder);
+        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+            // TODO: This branch occurs when the workspace is adding views
+            // outside of the defined grid
+            // maybe we should be deleting these items from the LauncherModel?
+            Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
+        }
+
+        if (!(child instanceof Folder)) {
+            child.setHapticFeedbackEnabled(false);
+            child.setOnLongClickListener(mLongClickListener);
+        }
+        if (child instanceof DropTarget) {
+            mDragController.addDropTarget((DropTarget) child);
+        }
+    }
+
+    /**
+     * Called directly from a CellLayout (not by the framework), after we've been added as a
+     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
+     * that it should intercept touch events, which is not something that is normally supported.
+     */
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        return (isSmall() || !isFinishedSwitchingState())
+                || (!isSmall() && indexOfChild(v) != mCurrentPage);
+    }
+
+    public boolean isSwitchingState() {
+        return mIsSwitchingState;
+    }
+
+    /** This differs from isSwitchingState in that we take into account how far the transition
+     *  has completed. */
+    public boolean isFinishedSwitchingState() {
+        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+    }
+
+    protected void onWindowVisibilityChanged (int visibility) {
+        mLauncher.onWindowVisibilityChanged(visibility);
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        if (isSmall() || !isFinishedSwitchingState()) {
+            // when the home screens are shrunken, shouldn't allow side-scrolling
+            return false;
+        }
+        return super.dispatchUnhandledMove(focused, direction);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+        case MotionEvent.ACTION_DOWN:
+            mXDown = ev.getX();
+            mYDown = ev.getY();
+            mTouchDownTime = System.currentTimeMillis();
+            break;
+        case MotionEvent.ACTION_POINTER_UP:
+        case MotionEvent.ACTION_UP:
+            if (mTouchState == TOUCH_STATE_REST) {
+                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
+                if (!currentPage.lastDownOnOccupiedCell()) {
+                    onWallpaperTap(ev);
+                }
+            }
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    protected void reinflateWidgetsIfNecessary() {
+        final int clCount = getChildCount();
+        for (int i = 0; i < clCount; i++) {
+            CellLayout cl = (CellLayout) getChildAt(i);
+            ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
+            final int itemCount = swc.getChildCount();
+            for (int j = 0; j < itemCount; j++) {
+                View v = swc.getChildAt(j);
+
+                if (v.getTag() instanceof LauncherAppWidgetInfo) {
+                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
+                    if (lahv != null && lahv.orientationChangedSincedInflation()) {
+                        mLauncher.removeAppWidget(info);
+                        // Remove the current widget which is inflated with the wrong orientation
+                        cl.removeView(lahv);
+                        mLauncher.bindAppWidget(info);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void determineScrollingStart(MotionEvent ev) {
+        if (!isFinishedSwitchingState()) return;
+
+        float deltaX = ev.getX() - mXDown;
+        float absDeltaX = Math.abs(deltaX);
+        float absDeltaY = Math.abs(ev.getY() - mYDown);
+
+        if (Float.compare(absDeltaX, 0f) == 0) return;
+
+        float slope = absDeltaY / absDeltaX;
+        float theta = (float) Math.atan(slope);
+
+        if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
+            cancelCurrentPageLongPress();
+        }
+
+        boolean passRightSwipesToCustomContent =
+                (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
+
+        boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
+        if (swipeInIgnoreDirection && getScreenIdForPageIndex(getCurrentPage()) ==
+                CUSTOM_CONTENT_SCREEN_ID && passRightSwipesToCustomContent) {
+            // Pass swipes to the right to the custom content page.
+            return;
+        }
+
+        if (theta > MAX_SWIPE_ANGLE) {
+            // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
+            return;
+        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+            // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
+            // increase the touch slop to make it harder to begin scrolling the workspace. This
+            // results in vertically scrolling widgets to more easily. The higher the angle, the
+            // more we increase touch slop.
+            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+            float extraRatio = (float)
+                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+        } else {
+            // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
+            super.determineScrollingStart(ev);
+        }
+    }
+
+    protected void onPageBeginMoving() {
+        super.onPageBeginMoving();
+
+        if (isHardwareAccelerated()) {
+            updateChildrenLayersEnabled(false);
+        } else {
+            if (mNextPage != INVALID_PAGE) {
+                // we're snapping to a particular screen
+                enableChildrenCache(mCurrentPage, mNextPage);
+            } else {
+                // this is when user is actively dragging a particular screen, they might
+                // swipe it either left or right (but we won't advance by more than one screen)
+                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
+            }
+        }
+
+        // Only show page outlines as we pan if we are on large screen
+        if (LauncherAppState.getInstance().isScreenLarge()) {
+            showOutlines();
+        }
+
+        // If we are not fading in adjacent screens, we still need to restore the alpha in case the
+        // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
+        if (!mWorkspaceFadeInAdjacentScreens) {
+            for (int i = 0; i < getChildCount(); ++i) {
+                ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
+            }
+        }
+    }
+
+    protected void onPageEndMoving() {
+        super.onPageEndMoving();
+
+        if (isHardwareAccelerated()) {
+            updateChildrenLayersEnabled(false);
+        } else {
+            clearChildrenCache();
+        }
+
+        if (mDragController.isDragging()) {
+            if (isSmall()) {
+                // If we are in springloaded mode, then force an event to check if the current touch
+                // is under a new page (to scroll to)
+                mDragController.forceTouchMove();
+            }
+        } else {
+            // If we are not mid-dragging, hide the page outlines if we are on a large screen
+            if (LauncherAppState.getInstance().isScreenLarge()) {
+                hideOutlines();
+            }
+        }
+
+        if (mDelayedResizeRunnable != null) {
+            mDelayedResizeRunnable.run();
+            mDelayedResizeRunnable = null;
+        }
+
+        if (mDelayedSnapToPageRunnable != null) {
+            mDelayedSnapToPageRunnable.run();
+            mDelayedSnapToPageRunnable = null;
+        }
+        if (mStripScreensOnPageStopMoving) {
+            stripEmptyScreens();
+            mStripScreensOnPageStopMoving = false;
+        }
+    }
+
+    @Override
+    protected void notifyPageSwitchListener() {
+        super.notifyPageSwitchListener();
+        Launcher.setScreen(mCurrentPage);
+
+        if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
+            mCustomContentShowing = true;
+            if (mCustomContentCallbacks != null) {
+                mCustomContentCallbacks.onShow();
+                mCustomContentShowTime = System.currentTimeMillis();
+                mLauncher.updateVoiceButtonProxyVisible(false);
+            }
+        } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
+            mCustomContentShowing = false;
+            if (mCustomContentCallbacks != null) {
+                mCustomContentCallbacks.onHide();
+                mLauncher.resetQSBScroll();
+                mLauncher.updateVoiceButtonProxyVisible(false);
+            }
+        }
+        if (getPageIndicator() != null) {
+            getPageIndicator().setContentDescription(getPageIndicatorDescription());
+        }
+    }
+
+    protected CustomContentCallbacks getCustomContentCallbacks() {
+        return mCustomContentCallbacks;
+    }
+
+    protected void setWallpaperDimension() {
+        String spKey = WallpaperCropActivity.getSharedPreferencesKey();
+        SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        WallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
+                sp, mLauncher.getWindowManager(), mWallpaperManager);
+    }
+
+    protected void snapToPage(int whichPage, Runnable r) {
+        if (mDelayedSnapToPageRunnable != null) {
+            mDelayedSnapToPageRunnable.run();
+        }
+        mDelayedSnapToPageRunnable = r;
+        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+    }
+
+    protected void snapToScreenId(long screenId, Runnable r) {
+        snapToPage(getPageIndexForScreenId(screenId), r);
+    }
+
+    class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
+        float mFinalOffset = 0.0f;
+        float mCurrentOffset = 0.5f; // to force an initial update
+        boolean mWaitingForUpdate;
+        Choreographer mChoreographer;
+        Interpolator mInterpolator;
+        boolean mAnimating;
+        long mAnimationStartTime;
+        float mAnimationStartOffset;
+        private final int ANIMATION_DURATION = 250;
+        // Don't use all the wallpaper for parallax until you have at least this many pages
+        private final int MIN_PARALLAX_PAGE_SPAN = 3;
+        int mNumScreens;
+
+        public WallpaperOffsetInterpolator() {
+            mChoreographer = Choreographer.getInstance();
+            mInterpolator = new DecelerateInterpolator(1.5f);
+        }
+
+        @Override
+        public void doFrame(long frameTimeNanos) {
+            updateOffset(false);
+        }
+
+        private void updateOffset(boolean force) {
+            if (mWaitingForUpdate || force) {
+                mWaitingForUpdate = false;
+                if (computeScrollOffset() && mWindowToken != null) {
+                    try {
+                        mWallpaperManager.setWallpaperOffsets(mWindowToken,
+                                mWallpaperOffset.getCurrX(), 0.5f);
+                        setWallpaperOffsetSteps();
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "Error updating wallpaper offset: " + e);
+                    }
+                }
+            }
+        }
+
+        public boolean computeScrollOffset() {
+            final float oldOffset = mCurrentOffset;
+            if (mAnimating) {
+                long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
+                float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
+                float t1 = mInterpolator.getInterpolation(t0);
+                mCurrentOffset = mAnimationStartOffset +
+                        (mFinalOffset - mAnimationStartOffset) * t1;
+                mAnimating = durationSinceAnimation < ANIMATION_DURATION;
+            } else {
+                mCurrentOffset = mFinalOffset;
+            }
+
+            if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
+                scheduleUpdate();
+            }
+            if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
+                return true;
+            }
+            return false;
+        }
+
+        private float wallpaperOffsetForCurrentScroll() {
+            if (getChildCount() <= 1) {
+                return 0;
+            }
+
+            // Exclude the leftmost page
+            int emptyExtraPages = numEmptyScreensToIgnore();
+            int firstIndex = numCustomPages();
+            // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
+            int lastIndex = getChildCount() - 1 - emptyExtraPages;
+            if (isLayoutRtl()) {
+                int temp = firstIndex;
+                firstIndex = lastIndex;
+                lastIndex = temp;
+            }
+
+            int firstPageScrollX = getScrollForPage(firstIndex);
+            int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
+            if (scrollRange == 0) {
+                return 0;
+            } else {
+                // TODO: do different behavior if it's  a live wallpaper?
+                // Sometimes the left parameter of the pages is animated during a layout transition;
+                // this parameter offsets it to keep the wallpaper from animating as well
+                int offsetForLayoutTransitionAnimation = isLayoutRtl() ?
+                        getPageAt(getChildCount() - 1).getLeft() - getFirstChildLeft() : 0;
+                int adjustedScroll =
+                        getScrollX() - firstPageScrollX - offsetForLayoutTransitionAnimation;
+                float offset = Math.min(1, adjustedScroll / (float) scrollRange);
+                offset = Math.max(0, offset);
+                // Don't use up all the wallpaper parallax until you have at least
+                // MIN_PARALLAX_PAGE_SPAN pages
+                int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
+                int parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+                // On RTL devices, push the wallpaper offset to the right if we don't have enough
+                // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
+                int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
+                return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
+            }
+        }
+
+        private int numEmptyScreensToIgnore() {
+            int numScrollingPages = getChildCount() - numCustomPages();
+            if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+
+        private int getNumScreensExcludingEmptyAndCustom() {
+            int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
+            return numScrollingPages;
+        }
+
+        public void syncWithScroll() {
+            float offset = wallpaperOffsetForCurrentScroll();
+            mWallpaperOffset.setFinalX(offset);
+            updateOffset(true);
+        }
+
+        public float getCurrX() {
+            return mCurrentOffset;
+        }
+
+        public float getFinalX() {
+            return mFinalOffset;
+        }
+
+        private void animateToFinal() {
+            mAnimating = true;
+            mAnimationStartOffset = mCurrentOffset;
+            mAnimationStartTime = System.currentTimeMillis();
+        }
+
+        private void setWallpaperOffsetSteps() {
+            // Set wallpaper offset steps (1 / (number of screens - 1))
+            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
+        }
+
+        public void setFinalX(float x) {
+            scheduleUpdate();
+            mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
+            if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
+                if (mNumScreens > 0) {
+                    // Don't animate if we're going from 0 screens
+                    animateToFinal();
+                }
+                mNumScreens = getNumScreensExcludingEmptyAndCustom();
+            }
+        }
+
+        private void scheduleUpdate() {
+            if (!mWaitingForUpdate) {
+                mChoreographer.postFrameCallback(this);
+                mWaitingForUpdate = true;
+            }
+        }
+
+        public void jumpToFinal() {
+            mCurrentOffset = mFinalOffset;
+        }
+    }
+
+    @Override
+    public void computeScroll() {
+        super.computeScroll();
+        mWallpaperOffset.syncWithScroll();
+    }
+
+    void showOutlines() {
+        if (!isSmall() && !mIsSwitchingState) {
+            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+            mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
+            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+            mChildrenOutlineFadeInAnimation.start();
+        }
+    }
+
+    void hideOutlines() {
+        if (!isSmall() && !mIsSwitchingState) {
+            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+            mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
+            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
+            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
+            mChildrenOutlineFadeOutAnimation.start();
+        }
+    }
+
+    public void showOutlinesTemporarily() {
+        if (!mIsPageMoving && !isTouchActive()) {
+            snapToPage(mCurrentPage);
+        }
+    }
+
+    public void setChildrenOutlineAlpha(float alpha) {
+        mChildrenOutlineAlpha = alpha;
+        for (int i = 0; i < getChildCount(); i++) {
+            CellLayout cl = (CellLayout) getChildAt(i);
+            cl.setBackgroundAlpha(alpha);
+        }
+    }
+
+    public float getChildrenOutlineAlpha() {
+        return mChildrenOutlineAlpha;
+    }
+
+    void disableBackground() {
+        mDrawBackground = false;
+    }
+    void enableBackground() {
+        mDrawBackground = true;
+    }
+
+    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
+        if (mBackground == null) return;
+        if (mBackgroundFadeInAnimation != null) {
+            mBackgroundFadeInAnimation.cancel();
+            mBackgroundFadeInAnimation = null;
+        }
+        if (mBackgroundFadeOutAnimation != null) {
+            mBackgroundFadeOutAnimation.cancel();
+            mBackgroundFadeOutAnimation = null;
+        }
+        float startAlpha = getBackgroundAlpha();
+        if (finalAlpha != startAlpha) {
+            if (animated) {
+                mBackgroundFadeOutAnimation =
+                        LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
+                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
+                    }
+                });
+                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
+                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+                mBackgroundFadeOutAnimation.start();
+            } else {
+                setBackgroundAlpha(finalAlpha);
+            }
+        }
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        if (alpha != mBackgroundAlpha) {
+            mBackgroundAlpha = alpha;
+            invalidate();
+        }
+    }
+
+    public float getBackgroundAlpha() {
+        return mBackgroundAlpha;
+    }
+
+    float backgroundAlphaInterpolator(float r) {
+        float pivotA = 0.1f;
+        float pivotB = 0.4f;
+        if (r < pivotA) {
+            return 0;
+        } else if (r > pivotB) {
+            return 1.0f;
+        } else {
+            return (r - pivotA)/(pivotB - pivotA);
+        }
+    }
+
+    private void updatePageAlphaValues(int screenCenter) {
+        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+        if (mWorkspaceFadeInAdjacentScreens &&
+                mState == State.NORMAL &&
+                !mIsSwitchingState &&
+                !isInOverscroll) {
+            for (int i = 0; i < getChildCount(); i++) {
+                CellLayout child = (CellLayout) getChildAt(i);
+                if (child != null) {
+                    float scrollProgress = getScrollProgress(screenCenter, child, i);
+                    float alpha = 1 - Math.abs(scrollProgress);
+                    child.getShortcutsAndWidgets().setAlpha(alpha);
+                    if (!mIsDragOccuring) {
+                        child.setBackgroundAlphaMultiplier(
+                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+                    } else {
+                        child.setBackgroundAlphaMultiplier(1f);
+                    }
+                }
+            }
+        }
+    }
+
+    private void setChildrenBackgroundAlphaMultipliers(float a) {
+        for (int i = 0; i < getChildCount(); i++) {
+            CellLayout child = (CellLayout) getChildAt(i);
+            child.setBackgroundAlphaMultiplier(a);
+        }
+    }
+
+    public boolean hasCustomContent() {
+        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
+    }
+
+    public int numCustomPages() {
+        return hasCustomContent() ? 1 : 0;
+    }
+
+    public boolean isOnOrMovingToCustomContent() {
+        return hasCustomContent() && getNextPage() == 0;
+    }
+
+    private void updateStateForCustomContent(int screenCenter) {
+        float translationX = 0;
+        float progress = 0;
+        if (hasCustomContent()) {
+            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
+
+            int scrollDelta = getScrollX() - getScrollForPage(index) -
+                    getLayoutTransitionOffsetForPage(index);
+            float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
+            translationX = scrollRange - scrollDelta;
+            progress = (scrollRange - scrollDelta) / scrollRange;
+
+            if (isLayoutRtl()) {
+                translationX = Math.min(0, translationX);
+            } else {
+                translationX = Math.max(0, translationX);
+            }
+            progress = Math.max(0, progress);
+        }
+
+        if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
+        mLastCustomContentScrollProgress = progress;
+
+        setBackgroundAlpha(progress * 0.8f);
+
+        if (mLauncher.getHotseat() != null) {
+            mLauncher.getHotseat().setTranslationX(translationX);
+        }
+
+        if (getPageIndicator() != null) {
+            getPageIndicator().setTranslationX(translationX);
+        }
+
+        if (mCustomContentCallbacks != null) {
+            mCustomContentCallbacks.onScrollProgressChanged(progress);
+        }
+    }
+
+    @Override
+    protected OnClickListener getPageIndicatorClickListener() {
+        AccessibilityManager am = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!am.isTouchExplorationEnabled()) {
+            return null;
+        }
+        OnClickListener listener = new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                enterOverviewMode();
+            }
+        };
+        return listener;
+    }
+
+    @Override
+    protected void screenScrolled(int screenCenter) {
+        final boolean isRtl = isLayoutRtl();
+        super.screenScrolled(screenCenter);
+
+        updatePageAlphaValues(screenCenter);
+        updateStateForCustomContent(screenCenter);
+        enableHwLayersOnVisiblePages();
+
+        boolean shouldOverScroll = (mOverScrollX < 0 && (!hasCustomContent() || isLayoutRtl())) ||
+                (mOverScrollX > mMaxScrollX && (!hasCustomContent() || !isLayoutRtl()));
+
+        if (shouldOverScroll) {
+            int index = 0;
+            float pivotX = 0f;
+            final float leftBiasedPivot = 0.25f;
+            final float rightBiasedPivot = 0.75f;
+            final int lowerIndex = 0;
+            final int upperIndex = getChildCount() - 1;
+
+            final boolean isLeftPage = mOverScrollX < 0;
+            index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
+            pivotX = isLeftPage ? rightBiasedPivot : leftBiasedPivot;
+
+            CellLayout cl = (CellLayout) getChildAt(index);
+            float scrollProgress = getScrollProgress(screenCenter, cl, index);
+            cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
+            float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
+            cl.setRotationY(rotation);
+
+            if (!mOverscrollTransformsSet || Float.compare(mLastOverscrollPivotX, pivotX) != 0) {
+                mOverscrollTransformsSet = true;
+                mLastOverscrollPivotX = pivotX;
+                cl.setCameraDistance(mDensity * mCameraDistance);
+                cl.setPivotX(cl.getMeasuredWidth() * pivotX);
+                cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
+                cl.setOverscrollTransformsDirty(true);
+            }
+        } else {
+            if (mOverscrollTransformsSet) {
+                mOverscrollTransformsSet = false;
+                ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
+                ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
+            }
+        }
+    }
+
+    @Override
+    protected void overScroll(float amount) {
+        acceleratedOverScroll(amount);
+    }
+
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mWindowToken = getWindowToken();
+        computeScroll();
+        mDragController.setWindowToken(mWindowToken);
+    }
+
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mWindowToken = null;
+    }
+
+    protected void onResume() {
+        if (getPageIndicator() != null) {
+            // In case accessibility state has changed, we need to perform this on every
+            // attach to window
+            OnClickListener listener = getPageIndicatorClickListener();
+            if (listener != null) {
+                getPageIndicator().setOnClickListener(listener);
+            }
+        }
+        AccessibilityManager am = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        sAccessibilityEnabled = am.isEnabled();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+            mWallpaperOffset.syncWithScroll();
+            mWallpaperOffset.jumpToFinal();
+        }
+        super.onLayout(changed, left, top, right, bottom);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // Draw the background gradient if necessary
+        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
+            int alpha = (int) (mBackgroundAlpha * 255);
+            mBackground.setAlpha(alpha);
+            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
+                    getMeasuredHeight());
+            mBackground.draw(canvas);
+        }
+
+        super.onDraw(canvas);
+
+        // Call back to LauncherModel to finish binding after the first draw
+        post(mBindPages);
+    }
+
+    boolean isDrawingBackgroundGradient() {
+        return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        if (!mLauncher.isAllAppsVisible()) {
+            final Folder openFolder = getOpenFolder();
+            if (openFolder != null) {
+                return openFolder.requestFocus(direction, previouslyFocusedRect);
+            } else {
+                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int getDescendantFocusability() {
+        if (isSmall()) {
+            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+        }
+        return super.getDescendantFocusability();
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if (!mLauncher.isAllAppsVisible()) {
+            final Folder openFolder = getOpenFolder();
+            if (openFolder != null) {
+                openFolder.addFocusables(views, direction);
+            } else {
+                super.addFocusables(views, direction, focusableMode);
+            }
+        }
+    }
+
+    public boolean isSmall() {
+        return mState == State.SMALL || mState == State.SPRING_LOADED || mState == State.OVERVIEW;
+    }
+
+    void enableChildrenCache(int fromPage, int toPage) {
+        if (fromPage > toPage) {
+            final int temp = fromPage;
+            fromPage = toPage;
+            toPage = temp;
+        }
+
+        final int screenCount = getChildCount();
+
+        fromPage = Math.max(fromPage, 0);
+        toPage = Math.min(toPage, screenCount - 1);
+
+        for (int i = fromPage; i <= toPage; i++) {
+            final CellLayout layout = (CellLayout) getChildAt(i);
+            layout.setChildrenDrawnWithCacheEnabled(true);
+            layout.setChildrenDrawingCacheEnabled(true);
+        }
+    }
+
+    void clearChildrenCache() {
+        final int screenCount = getChildCount();
+        for (int i = 0; i < screenCount; i++) {
+            final CellLayout layout = (CellLayout) getChildAt(i);
+            layout.setChildrenDrawnWithCacheEnabled(false);
+            // In software mode, we don't want the items to continue to be drawn into bitmaps
+            if (!isHardwareAccelerated()) {
+                layout.setChildrenDrawingCacheEnabled(false);
+            }
+        }
+    }
+
+    private void updateChildrenLayersEnabled(boolean force) {
+        boolean small = mState == State.SMALL || mState == State.OVERVIEW || mIsSwitchingState;
+        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+
+        if (enableChildrenLayers != mChildrenLayersEnabled) {
+            mChildrenLayersEnabled = enableChildrenLayers;
+            if (mChildrenLayersEnabled) {
+                enableHwLayersOnVisiblePages();
+            } else {
+                for (int i = 0; i < getPageCount(); i++) {
+                    final CellLayout cl = (CellLayout) getChildAt(i);
+                    cl.enableHardwareLayer(false);
+                }
+            }
+        }
+    }
+
+    private void enableHwLayersOnVisiblePages() {
+        if (mChildrenLayersEnabled) {
+            final int screenCount = getChildCount();
+            getVisiblePages(mTempVisiblePagesRange);
+            int leftScreen = mTempVisiblePagesRange[0];
+            int rightScreen = mTempVisiblePagesRange[1];
+            if (leftScreen == rightScreen) {
+                // make sure we're caching at least two pages always
+                if (rightScreen < screenCount - 1) {
+                    rightScreen++;
+                } else if (leftScreen > 0) {
+                    leftScreen--;
+                }
+            }
+
+            final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
+            for (int i = 0; i < screenCount; i++) {
+                final CellLayout layout = (CellLayout) getPageAt(i);
+
+                // enable layers between left and right screen inclusive, except for the
+                // customScreen, which may animate its content during transitions.
+                boolean enableLayer = layout != customScreen &&
+                        leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
+                layout.enableHardwareLayer(enableLayer);
+            }
+        }
+    }
+
+    public void buildPageHardwareLayers() {
+        // force layers to be enabled just for the call to buildLayer
+        updateChildrenLayersEnabled(true);
+        if (getWindowToken() != null) {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                CellLayout cl = (CellLayout) getChildAt(i);
+                cl.buildHardwareLayer();
+            }
+        }
+        updateChildrenLayersEnabled(false);
+    }
+
+    protected void onWallpaperTap(MotionEvent ev) {
+        final int[] position = mTempCell;
+        getLocationOnScreen(position);
+
+        int pointerIndex = ev.getActionIndex();
+        position[0] += (int) ev.getX(pointerIndex);
+        position[1] += (int) ev.getY(pointerIndex);
+
+        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+                ev.getAction() == MotionEvent.ACTION_UP
+                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
+                position[0], position[1], 0, null);
+    }
+
+    /*
+     * This interpolator emulates the rate at which the perceived scale of an object changes
+     * as its distance from a camera increases. When this interpolator is applied to a scale
+     * animation on a view, it evokes the sense that the object is shrinking due to moving away
+     * from the camera.
+     */
+    static class ZInterpolator implements TimeInterpolator {
+        private float focalLength;
+
+        public ZInterpolator(float foc) {
+            focalLength = foc;
+        }
+
+        public float getInterpolation(float input) {
+            return (1.0f - focalLength / (focalLength + input)) /
+                (1.0f - focalLength / (focalLength + 1.0f));
+        }
+    }
+
+    /*
+     * The exact reverse of ZInterpolator.
+     */
+    static class InverseZInterpolator implements TimeInterpolator {
+        private ZInterpolator zInterpolator;
+        public InverseZInterpolator(float foc) {
+            zInterpolator = new ZInterpolator(foc);
+        }
+        public float getInterpolation(float input) {
+            return 1 - zInterpolator.getInterpolation(1 - input);
+        }
+    }
+
+    /*
+     * ZInterpolator compounded with an ease-out.
+     */
+    static class ZoomOutInterpolator implements TimeInterpolator {
+        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
+        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
+
+        public float getInterpolation(float input) {
+            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
+        }
+    }
+
+    /*
+     * InvereZInterpolator compounded with an ease-out.
+     */
+    static class ZoomInInterpolator implements TimeInterpolator {
+        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
+        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
+
+        public float getInterpolation(float input) {
+            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
+        }
+    }
+
+    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
+
+    /*
+    *
+    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
+    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
+    *
+    * These methods mark the appropriate pages as accepting drops (which alters their visual
+    * appearance).
+    *
+    */
+    public void onDragStartedWithItem(View v) {
+        final Canvas canvas = new Canvas();
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
+    }
+
+    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
+        final Canvas canvas = new Canvas();
+
+        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
+                size[1], clipAlpha);
+    }
+
+    public void exitWidgetResizeMode() {
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        dragLayer.clearAllResizeFrames();
+    }
+
+    private void initAnimationArrays() {
+        final int childCount = getChildCount();
+        if (mLastChildCount == childCount) return;
+
+        mOldBackgroundAlphas = new float[childCount];
+        mOldAlphas = new float[childCount];
+        mNewBackgroundAlphas = new float[childCount];
+        mNewAlphas = new float[childCount];
+    }
+
+    Animator getChangeStateAnimation(final State state, boolean animated) {
+        return getChangeStateAnimation(state, animated, 0, -1);
+    }
+
+    @Override
+    protected void getOverviewModePages(int[] range) {
+        int start = numCustomPages();
+        int end = getChildCount() - 1;
+
+        range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
+        range[1] = Math.max(0,  end);
+     }
+
+    protected void onStartReordering() {
+        super.onStartReordering();
+        showOutlines();
+        // Reordering handles its own animations, disable the automatic ones.
+        disableLayoutTransitions();
+    }
+
+    protected void onEndReordering() {
+        super.onEndReordering();
+
+        hideOutlines();
+        mScreenOrder.clear();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            CellLayout cl = ((CellLayout) getChildAt(i));
+            mScreenOrder.add(getIdForScreen(cl));
+        }
+
+        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+
+        // Re-enable auto layout transitions for page deletion.
+        enableLayoutTransitions();
+    }
+
+    public boolean isInOverviewMode() {
+        return mState == State.OVERVIEW;
+    }
+
+    public boolean enterOverviewMode() {
+        if (mTouchState != TOUCH_STATE_REST) {
+            return false;
+        }
+        enableOverviewMode(true, -1, true);
+        return true;
+    }
+
+    public void exitOverviewMode(boolean animated) {
+        exitOverviewMode(-1, animated);
+    }
+
+    public void exitOverviewMode(int snapPage, boolean animated) {
+        enableOverviewMode(false, snapPage, animated);
+    }
+
+    private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
+        State finalState = Workspace.State.OVERVIEW;
+        if (!enable) {
+            finalState = Workspace.State.NORMAL;
+        }
+
+        Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
+        if (workspaceAnim != null) {
+            onTransitionPrepare();
+            workspaceAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator arg0) {
+                    onTransitionEnd();
+                }
+            });
+            workspaceAnim.start();
+        }
+    }
+
+    int getOverviewModeTranslationY() {
+        int childHeight = getNormalChildHeight();
+        int viewPortHeight = getViewportHeight();
+        int scaledChildHeight = (int) (mOverviewModeShrinkFactor * childHeight);
+
+        int offset = (viewPortHeight - scaledChildHeight) / 2;
+        int offsetDelta = mOverviewModePageOffset - offset + mInsets.top;
+
+        return offsetDelta;
+    }
+
+    boolean shouldVoiceButtonProxyBeVisible() {
+        if (isOnOrMovingToCustomContent()) {
+            return false;
+        }
+        if (mState != State.NORMAL) {
+            return false;
+        }
+        return true;
+    }
+
+    public void updateInteractionForState() {
+        if (mState != State.NORMAL) {
+            mLauncher.onInteractionBegin();
+        } else {
+            mLauncher.onInteractionEnd();
+        }
+    }
+
+    private void setState(State state) {
+        mState = state;
+        updateInteractionForState();
+        updateAccessibilityFlags();
+    }
+
+    private void updateAccessibilityFlags() {
+        int accessible = mState == State.NORMAL ?
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+        setImportantForAccessibility(accessible);
+    }
+
+    Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
+        if (mState == state) {
+            return null;
+        }
+
+        // Initialize animation arrays for the first time if necessary
+        initAnimationArrays();
+
+        AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
+
+        final State oldState = mState;
+        final boolean oldStateIsNormal = (oldState == State.NORMAL);
+        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
+        final boolean oldStateIsSmall = (oldState == State.SMALL);
+        final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
+        setState(state);
+        final boolean stateIsNormal = (state == State.NORMAL);
+        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
+        final boolean stateIsSmall = (state == State.SMALL);
+        final boolean stateIsOverview = (state == State.OVERVIEW);
+        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
+        float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
+        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
+        float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
+        float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
+
+        boolean workspaceToAllApps = (oldStateIsNormal && stateIsSmall);
+        boolean allAppsToWorkspace = (oldStateIsSmall && stateIsNormal);
+        boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
+        boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
+
+        mNewScale = 1.0f;
+
+        if (oldStateIsOverview) {
+            disableFreeScroll(snapPage);
+        } else if (stateIsOverview) {
+            enableFreeScroll();
+        }
+
+        if (state != State.NORMAL) {
+            if (stateIsSpringLoaded) {
+                mNewScale = mSpringLoadedShrinkFactor;
+            } else if (stateIsOverview) {
+                mNewScale = mOverviewModeShrinkFactor;
+            } else if (stateIsSmall){
+                mNewScale = mOverviewModeShrinkFactor - 0.3f;
+            }
+            if (workspaceToAllApps) {
+                updateChildrenLayersEnabled(false);
+            }
+        }
+
+        final int duration;
+        if (workspaceToAllApps) {
+            duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
+        } else if (workspaceToOverview || overviewToWorkspace) {
+            duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
+        } else {
+            duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
+        }
+
+        for (int i = 0; i < getChildCount(); i++) {
+            final CellLayout cl = (CellLayout) getChildAt(i);
+            boolean isCurrentPage = (i == getNextPage());
+            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
+            float finalAlpha = stateIsSmall ? 0f : 1f;
+
+            // If we are animating to/from the small state, then hide the side pages and fade the
+            // current page in
+            if (!mIsSwitchingState) {
+                if (workspaceToAllApps || allAppsToWorkspace) {
+                    if (allAppsToWorkspace && isCurrentPage) {
+                        initialAlpha = 0f;
+                    } else if (!isCurrentPage) {
+                        initialAlpha = finalAlpha = 0f;
+                    }
+                    cl.setShortcutAndWidgetAlpha(initialAlpha);
+                }
+            }
+
+            mOldAlphas[i] = initialAlpha;
+            mNewAlphas[i] = finalAlpha;
+            if (animated) {
+                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
+                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
+            } else {
+                cl.setBackgroundAlpha(finalBackgroundAlpha);
+                cl.setShortcutAndWidgetAlpha(finalAlpha);
+            }
+        }
+
+        final View searchBar = mLauncher.getQsbBar();
+        final View overviewPanel = mLauncher.getOverviewPanel();
+        final View hotseat = mLauncher.getHotseat();
+        if (animated) {
+            anim.setDuration(duration);
+            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
+            scale.scaleX(mNewScale)
+                .scaleY(mNewScale)
+                .translationY(finalWorkspaceTranslationY)
+                .setInterpolator(mZoomInInterpolator);
+            anim.play(scale);
+            for (int index = 0; index < getChildCount(); index++) {
+                final int i = index;
+                final CellLayout cl = (CellLayout) getChildAt(i);
+                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
+                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
+                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
+                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
+                } else {
+                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
+                        LauncherViewPropertyAnimator alphaAnim =
+                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+                        alphaAnim.alpha(mNewAlphas[i])
+                            .setInterpolator(mZoomInInterpolator);
+                        anim.play(alphaAnim);
+                    }
+                    if (mOldBackgroundAlphas[i] != 0 ||
+                        mNewBackgroundAlphas[i] != 0) {
+                        ValueAnimator bgAnim =
+                                LauncherAnimUtils.ofFloat(cl, 0f, 1f);
+                        bgAnim.setInterpolator(mZoomInInterpolator);
+                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+                                public void onAnimationUpdate(float a, float b) {
+                                    cl.setBackgroundAlpha(
+                                            a * mOldBackgroundAlphas[i] +
+                                            b * mNewBackgroundAlphas[i]);
+                                }
+                            });
+                        anim.play(bgAnim);
+                    }
+                }
+            }
+            ObjectAnimator pageIndicatorAlpha = null;
+            if (getPageIndicator() != null) {
+                pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
+                        finalHotseatAndPageIndicatorAlpha);
+            }
+            ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
+                    finalHotseatAndPageIndicatorAlpha);
+            ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
+                    "alpha", finalSearchBarAlpha);
+            ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
+                    "alpha", finalOverviewPanelAlpha);
+
+            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
+            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
+            searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
+
+            if (workspaceToOverview) {
+                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
+            } else if (overviewToWorkspace) {
+                overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
+            }
+
+            if (getPageIndicator() != null) {
+                pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
+            }
+
+            anim.play(overviewPanelAlpha);
+            anim.play(hotseatAlpha);
+            anim.play(searchBarAlpha);
+            anim.play(pageIndicatorAlpha);
+            anim.setStartDelay(delay);
+        } else {
+            overviewPanel.setAlpha(finalOverviewPanelAlpha);
+            AlphaUpdateListener.updateVisibility(overviewPanel);
+            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
+            AlphaUpdateListener.updateVisibility(hotseat);
+            if (getPageIndicator() != null) {
+                getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
+                AlphaUpdateListener.updateVisibility(getPageIndicator());
+            }
+            searchBar.setAlpha(finalSearchBarAlpha);
+            AlphaUpdateListener.updateVisibility(searchBar);
+            updateCustomContentVisibility();
+            setScaleX(mNewScale);
+            setScaleY(mNewScale);
+            setTranslationY(finalWorkspaceTranslationY);
+        }
+        mLauncher.updateVoiceButtonProxyVisible(false);
+
+        if (stateIsSpringLoaded) {
+            // Right now we're covered by Apps Customize
+            // Show the background gradient immediately, so the gradient will
+            // be showing once AppsCustomize disappears
+            animateBackgroundGradient(getResources().getInteger(
+                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
+        } else if (stateIsOverview) {
+            animateBackgroundGradient(getResources().getInteger(
+                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true);
+        } else {
+            // Fade the background gradient away
+            animateBackgroundGradient(0f, animated);
+        }
+        return anim;
+    }
+
+    static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
+        View view;
+        public AlphaUpdateListener(View v) {
+            view = v;
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator arg0) {
+            updateVisibility(view);
+        }
+
+        public static void updateVisibility(View view) {
+            // We want to avoid the extra layout pass by setting the views to GONE unless
+            // accessibility is on, in which case not setting them to GONE causes a glitch.
+            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
+            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
+                view.setVisibility(invisibleState);
+            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
+                    && view.getVisibility() != VISIBLE) {
+                view.setVisibility(VISIBLE);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator arg0) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator arg0) {
+            updateVisibility(view);
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator arg0) {
+        }
+
+        @Override
+        public void onAnimationStart(Animator arg0) {
+            // We want the views to be visible for animation, so fade-in/out is visible
+            view.setVisibility(VISIBLE);
+        }
+    }
+
+    @Override
+    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+        onTransitionPrepare();
+    }
+
+    @Override
+    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+    }
+
+    @Override
+    public void onLauncherTransitionStep(Launcher l, float t) {
+        mTransitionProgress = t;
+    }
+
+    @Override
+    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+        onTransitionEnd();
+    }
+
+    private void onTransitionPrepare() {
+        mIsSwitchingState = true;
+
+        // Invalidate here to ensure that the pages are rendered during the state change transition.
+        invalidate();
+
+        updateChildrenLayersEnabled(false);
+        hideCustomContentIfNecessary();
+    }
+
+    void updateCustomContentVisibility() {
+        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
+        if (hasCustomContent()) {
+            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
+        }
+    }
+
+    void showCustomContentIfNecessary() {
+        boolean show  = mState == Workspace.State.NORMAL;
+        if (show && hasCustomContent()) {
+            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
+        }
+    }
+
+    void hideCustomContentIfNecessary() {
+        boolean hide  = mState != Workspace.State.NORMAL;
+        if (hide && hasCustomContent()) {
+            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
+        }
+    }
+
+    private void onTransitionEnd() {
+        mIsSwitchingState = false;
+        updateChildrenLayersEnabled(false);
+        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
+        // ensure that only the current page is visible during (and subsequently, after) the
+        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
+        // visibility after the transition animation.
+        if (!mWorkspaceFadeInAdjacentScreens) {
+            for (int i = 0; i < getChildCount(); i++) {
+                final CellLayout cl = (CellLayout) getChildAt(i);
+                cl.setShortcutAndWidgetAlpha(1f);
+            }
+        }
+        showCustomContentIfNecessary();
+    }
+
+    @Override
+    public View getContent() {
+        return this;
+    }
+
+    /**
+     * Draw the View v into the given Canvas.
+     *
+     * @param v the view to draw
+     * @param destCanvas the canvas to draw on
+     * @param padding the horizontal and vertical padding to use when drawing
+     */
+    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
+        final Rect clipRect = mTempRect;
+        v.getDrawingRect(clipRect);
+
+        boolean textVisible = false;
+
+        destCanvas.save();
+        if (v instanceof TextView && pruneToDrawable) {
+            Drawable d = ((TextView) v).getCompoundDrawables()[1];
+            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
+            destCanvas.translate(padding / 2, padding / 2);
+            d.draw(destCanvas);
+        } else {
+            if (v instanceof FolderIcon) {
+                // For FolderIcons the text can bleed into the icon area, and so we need to
+                // hide the text completely (which can't be achieved by clipping).
+                if (((FolderIcon) v).getTextVisible()) {
+                    ((FolderIcon) v).setTextVisible(false);
+                    textVisible = true;
+                }
+            } else if (v instanceof BubbleTextView) {
+                final BubbleTextView tv = (BubbleTextView) v;
+                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
+                        tv.getLayout().getLineTop(0);
+            } else if (v instanceof TextView) {
+                final TextView tv = (TextView) v;
+                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
+                        tv.getLayout().getLineTop(0);
+            }
+            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+            destCanvas.clipRect(clipRect, Op.REPLACE);
+            v.draw(destCanvas);
+
+            // Restore text visibility of FolderIcon if necessary
+            if (textVisible) {
+                ((FolderIcon) v).setTextVisible(true);
+            }
+        }
+        destCanvas.restore();
+    }
+
+    /**
+     * Returns a new bitmap to show when the given View is being dragged around.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+        Bitmap b;
+
+        if (v instanceof TextView) {
+            Drawable d = ((TextView) v).getCompoundDrawables()[1];
+            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
+                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
+        } else {
+            b = Bitmap.createBitmap(
+                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+        }
+
+        canvas.setBitmap(b);
+        drawDragView(v, canvas, padding, true);
+        canvas.setBitmap(null);
+
+        return b;
+    }
+
+    /**
+     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+        final int outlineColor = getResources().getColor(R.color.outline_color);
+        final Bitmap b = Bitmap.createBitmap(
+                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        drawDragView(v, canvas, padding, true);
+        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
+        canvas.setBitmap(null);
+        return b;
+    }
+
+    /**
+     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
+            boolean clipAlpha) {
+        final int outlineColor = getResources().getColor(R.color.outline_color);
+        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+        canvas.setBitmap(b);
+
+        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
+        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
+                (h - padding) / (float) orig.getHeight());
+        int scaledWidth = (int) (scaleFactor * orig.getWidth());
+        int scaledHeight = (int) (scaleFactor * orig.getHeight());
+        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
+
+        // center the image
+        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
+
+        canvas.drawBitmap(orig, src, dst, null);
+        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
+                clipAlpha);
+        canvas.setBitmap(null);
+
+        return b;
+    }
+
+    void startDrag(CellLayout.CellInfo cellInfo) {
+        View child = cellInfo.cell;
+
+        // Make sure the drag was started by a long press as opposed to a long click.
+        if (!child.isInTouchMode()) {
+            return;
+        }
+
+        mDragInfo = cellInfo;
+        child.setVisibility(INVISIBLE);
+        CellLayout layout = (CellLayout) child.getParent().getParent();
+        layout.prepareChildForDrag(child);
+
+        child.clearFocus();
+        child.setPressed(false);
+
+        final Canvas canvas = new Canvas();
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
+        beginDragShared(child, this);
+    }
+
+    public void beginDragShared(View child, DragSource source) {
+        // The drag bitmap follows the touch point around on the screen
+        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
+
+        final int bmpWidth = b.getWidth();
+        final int bmpHeight = b.getHeight();
+
+        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
+        int dragLayerX =
+                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
+        int dragLayerY =
+                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
+                        - DRAG_BITMAP_PADDING / 2);
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        Point dragVisualizeOffset = null;
+        Rect dragRect = null;
+        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
+            int iconSize = grid.iconSizePx;
+            int top = child.getPaddingTop();
+            int left = (bmpWidth - iconSize) / 2;
+            int right = left + iconSize;
+            int bottom = top + iconSize;
+            dragLayerY += top;
+            // Note: The drag region is used to calculate drag layer offsets, but the
+            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
+            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+            dragRect = new Rect(left, top, right, bottom);
+        } else if (child instanceof FolderIcon) {
+            int previewSize = grid.folderIconSizePx;
+            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
+        }
+
+        // Clear the pressed state if necessary
+        if (child instanceof BubbleTextView) {
+            BubbleTextView icon = (BubbleTextView) child;
+            icon.clearPressedOrFocusedBackground();
+        }
+
+        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+
+        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
+            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
+        }
+
+        b.recycle();
+    }
+
+    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
+            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
+        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
+
+        final int[] cellXY = new int[2];
+        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
+        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
+
+        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
+                cellXY[1]);
+    }
+
+    public boolean transitionStateShouldAllowDrop() {
+        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean acceptDrop(DragObject d) {
+        // If it's an external drop (e.g. from All Apps), check if it should be accepted
+        CellLayout dropTargetLayout = mDropToLayout;
+        if (d.dragSource != this) {
+            // Don't accept the drop if we're not over a screen at time of drop
+            if (dropTargetLayout == null) {
+                return false;
+            }
+            if (!transitionStateShouldAllowDrop()) return false;
+
+            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+                    d.dragView, mDragViewVisualCenter);
+
+            // We want the point to be mapped to the dragTarget.
+            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+            } else {
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+            }
+
+            int spanX = 1;
+            int spanY = 1;
+            if (mDragInfo != null) {
+                final CellLayout.CellInfo dragCellInfo = mDragInfo;
+                spanX = dragCellInfo.spanX;
+                spanY = dragCellInfo.spanY;
+            } else {
+                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
+                spanX = dragInfo.spanX;
+                spanY = dragInfo.spanY;
+            }
+
+            int minSpanX = spanX;
+            int minSpanY = spanY;
+            if (d.dragInfo instanceof PendingAddWidgetInfo) {
+                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
+                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
+            }
+
+            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
+                    mTargetCell);
+            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+                    mDragViewVisualCenter[1], mTargetCell);
+            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+                    mTargetCell, distance, true)) {
+                return true;
+            }
+            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+                    mTargetCell, distance)) {
+                return true;
+            }
+
+            int[] resultSpan = new int[2];
+            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
+            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+            // Don't accept the drop if there's no room for the item
+            if (!foundCell) {
+                // Don't show the message if we are dropping on the AllApps button and the hotseat
+                // is full
+                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+                if (mTargetCell != null && isHotseat) {
+                    Hotseat hotseat = mLauncher.getHotseat();
+                    if (hotseat.isAllAppsButtonRank(
+                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
+                        return false;
+                    }
+                }
+
+                mLauncher.showOutOfSpaceMessage(isHotseat);
+                return false;
+            }
+        }
+
+        long screenId = getIdForScreen(dropTargetLayout);
+        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+            commitExtraEmptyScreen();
+        }
+
+        return true;
+    }
+
+    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
+            distance, boolean considerTimeout) {
+        if (distance > mMaxDistanceForFolderCreation) return false;
+        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+        if (dropOverView != null) {
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+                return false;
+            }
+        }
+
+        boolean hasntMoved = false;
+        if (mDragInfo != null) {
+            hasntMoved = dropOverView == mDragInfo.cell;
+        }
+
+        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
+            return false;
+        }
+
+        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
+        boolean willBecomeShortcut =
+                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+
+        return (aboveShortcut && willBecomeShortcut);
+    }
+
+    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
+            float distance) {
+        if (distance > mMaxDistanceForFolderCreation) return false;
+        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+        if (dropOverView != null) {
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+                return false;
+            }
+        }
+
+        if (dropOverView instanceof FolderIcon) {
+            FolderIcon fi = (FolderIcon) dropOverView;
+            if (fi.acceptDrop(dragInfo)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
+            int[] targetCell, float distance, boolean external, DragView dragView,
+            Runnable postAnimationRunnable) {
+        if (distance > mMaxDistanceForFolderCreation) return false;
+        View v = target.getChildAt(targetCell[0], targetCell[1]);
+
+        boolean hasntMoved = false;
+        if (mDragInfo != null) {
+            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
+            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
+                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
+        }
+
+        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
+        mCreateUserFolderOnDrop = false;
+        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
+
+        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
+        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
+
+        if (aboveShortcut && willBecomeShortcut) {
+            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
+            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
+            // if the drag started here, we need to remove it from the workspace
+            if (!external) {
+                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+            }
+
+            Rect folderLocation = new Rect();
+            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
+            target.removeView(v);
+
+            FolderIcon fi =
+                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
+            destInfo.cellX = -1;
+            destInfo.cellY = -1;
+            sourceInfo.cellX = -1;
+            sourceInfo.cellY = -1;
+
+            // If the dragView is null, we can't animate
+            boolean animate = dragView != null;
+            if (animate) {
+                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
+                        postAnimationRunnable);
+            } else {
+                fi.addItem(destInfo);
+                fi.addItem(sourceInfo);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
+            float distance, DragObject d, boolean external) {
+        if (distance > mMaxDistanceForFolderCreation) return false;
+
+        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+        if (!mAddToExistingFolderOnDrop) return false;
+        mAddToExistingFolderOnDrop = false;
+
+        if (dropOverView instanceof FolderIcon) {
+            FolderIcon fi = (FolderIcon) dropOverView;
+            if (fi.acceptDrop(d.dragInfo)) {
+                fi.onDrop(d);
+
+                // if the drag started here, we need to remove it from the workspace
+                if (!external) {
+                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void onDrop(final DragObject d) {
+        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
+                mDragViewVisualCenter);
+
+        CellLayout dropTargetLayout = mDropToLayout;
+
+        // We want the point to be mapped to the dragTarget.
+        if (dropTargetLayout != null) {
+            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+            } else {
+                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+            }
+        }
+
+        int snapScreen = -1;
+        boolean resizeOnDrop = false;
+        if (d.dragSource != this) {
+            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1] };
+            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+        } else if (mDragInfo != null) {
+            final View cell = mDragInfo.cell;
+
+            Runnable resizeRunnable = null;
+            if (dropTargetLayout != null && !d.cancelled) {
+                // Move internally
+                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
+                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+                long container = hasMovedIntoHotseat ?
+                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
+                long screenId = (mTargetCell[0] < 0) ?
+                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
+                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
+                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
+                // First we find the cell nearest to point at which the item is
+                // dropped, without any consideration to whether there is an item there.
+
+                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
+                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
+                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+                        mDragViewVisualCenter[1], mTargetCell);
+
+                // If the item being dropped is a shortcut and the nearest drop
+                // cell also contains a shortcut, then create a folder with the two shortcuts.
+                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
+                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
+                    stripEmptyScreens();
+                    return;
+                }
+
+                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+                        distance, d, false)) {
+                    stripEmptyScreens();
+                    return;
+                }
+
+                // Aside from the special case where we're dropping a shortcut onto a shortcut,
+                // we need to find the nearest cell location that is vacant
+                ItemInfo item = (ItemInfo) d.dragInfo;
+                int minSpanX = item.spanX;
+                int minSpanY = item.spanY;
+                if (item.minSpanX > 0 && item.minSpanY > 0) {
+                    minSpanX = item.minSpanX;
+                    minSpanY = item.minSpanY;
+                }
+
+                int[] resultSpan = new int[2];
+                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+
+                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+                // if the widget resizes on drop
+                if (foundCell && (cell instanceof AppWidgetHostView) &&
+                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
+                    resizeOnDrop = true;
+                    item.spanX = resultSpan[0];
+                    item.spanY = resultSpan[1];
+                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
+                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
+                            resultSpan[1]);
+                }
+
+                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
+                    snapScreen = getPageIndexForScreenId(screenId);
+                    snapToPage(snapScreen);
+                }
+
+                if (foundCell) {
+                    final ItemInfo info = (ItemInfo) cell.getTag();
+                    if (hasMovedLayouts) {
+                        // Reparent the view
+                        getParentCellLayoutForView(cell).removeView(cell);
+                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
+                                info.spanX, info.spanY);
+                    }
+
+                    // update the item's position after drop
+                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    lp.cellX = lp.tmpCellX = mTargetCell[0];
+                    lp.cellY = lp.tmpCellY = mTargetCell[1];
+                    lp.cellHSpan = item.spanX;
+                    lp.cellVSpan = item.spanY;
+                    lp.isLockedToGrid = true;
+                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
+                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
+
+                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+                            cell instanceof LauncherAppWidgetHostView) {
+                        final CellLayout cellLayout = dropTargetLayout;
+                        // We post this call so that the widget has a chance to be placed
+                        // in its final location
+
+                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
+                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
+                        if (pinfo != null &&
+                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+                            final Runnable addResizeFrame = new Runnable() {
+                                public void run() {
+                                    DragLayer dragLayer = mLauncher.getDragLayer();
+                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
+                                }
+                            };
+                            resizeRunnable = (new Runnable() {
+                                public void run() {
+                                    if (!isPageMoving()) {
+                                        addResizeFrame.run();
+                                    } else {
+                                        mDelayedResizeRunnable = addResizeFrame;
+                                    }
+                                }
+                            });
+                        }
+                    }
+
+                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
+                            lp.cellY, item.spanX, item.spanY);
+                } else {
+                    // If we can't find a drop location, we return the item to its original position
+                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    mTargetCell[0] = lp.cellX;
+                    mTargetCell[1] = lp.cellY;
+                    CellLayout layout = (CellLayout) cell.getParent().getParent();
+                    layout.markCellsAsOccupiedForView(cell);
+                }
+            }
+
+            final CellLayout parent = (CellLayout) cell.getParent().getParent();
+            final Runnable finalResizeRunnable = resizeRunnable;
+            // Prepare it to be animated into its new position
+            // This must be called after the view has been re-parented
+            final Runnable onCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    mAnimatingViewIntoPlace = false;
+                    updateChildrenLayersEnabled(false);
+                    if (finalResizeRunnable != null) {
+                        finalResizeRunnable.run();
+                    }
+                    stripEmptyScreens();
+                }
+            };
+            mAnimatingViewIntoPlace = true;
+            if (d.dragView.hasDrawn()) {
+                final ItemInfo info = (ItemInfo) cell.getTag();
+                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
+                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+                    animateWidgetDrop(info, parent, d.dragView,
+                            onCompleteRunnable, animationType, cell, false);
+                } else {
+                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
+                            onCompleteRunnable, this);
+                }
+            } else {
+                d.deferDragViewCleanupPostAnimation = false;
+                cell.setVisibility(VISIBLE);
+            }
+            parent.onDropChild(cell);
+        }
+    }
+
+    public void setFinalScrollForPageChange(int pageIndex) {
+        CellLayout cl = (CellLayout) getChildAt(pageIndex);
+        if (cl != null) {
+            mSavedScrollX = getScrollX();
+            mSavedTranslationX = cl.getTranslationX();
+            mSavedRotationY = cl.getRotationY();
+            final int newX = getScrollForPage(pageIndex);
+            setScrollX(newX);
+            cl.setTranslationX(0f);
+            cl.setRotationY(0f);
+        }
+    }
+
+    public void resetFinalScrollForPageChange(int pageIndex) {
+        if (pageIndex >= 0) {
+            CellLayout cl = (CellLayout) getChildAt(pageIndex);
+            setScrollX(mSavedScrollX);
+            cl.setTranslationX(mSavedTranslationX);
+            cl.setRotationY(mSavedRotationY);
+        }
+    }
+
+    public void getViewLocationRelativeToSelf(View v, int[] location) {
+        getLocationInWindow(location);
+        int x = location[0];
+        int y = location[1];
+
+        v.getLocationInWindow(location);
+        int vX = location[0];
+        int vY = location[1];
+
+        location[0] = vX - x;
+        location[1] = vY - y;
+    }
+
+    public void onDragEnter(DragObject d) {
+        mDragEnforcer.onDragEnter();
+        mCreateUserFolderOnDrop = false;
+        mAddToExistingFolderOnDrop = false;
+
+        mDropToLayout = null;
+        CellLayout layout = getCurrentDropLayout();
+        setCurrentDropLayout(layout);
+        setCurrentDragOverlappingLayout(layout);
+
+        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
+        // don't need to show the outlines
+        if (LauncherAppState.getInstance().isScreenLarge()) {
+            showOutlines();
+        }
+    }
+
+    /** Return a rect that has the cellWidth/cellHeight (left, top), and
+     * widthGap/heightGap (right, bottom) */
+    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        Resources res = launcher.getResources();
+        Display display = launcher.getWindowManager().getDefaultDisplay();
+        Point smallestSize = new Point();
+        Point largestSize = new Point();
+        display.getCurrentSizeRange(smallestSize, largestSize);
+        int countX = (int) grid.numColumns;
+        int countY = (int) grid.numRows;
+        int constrainedLongEdge = largestSize.y;
+        int constrainedShortEdge = smallestSize.y;
+        if (orientation == CellLayout.LANDSCAPE) {
+            if (mLandscapeCellLayoutMetrics == null) {
+                Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
+                int width = constrainedLongEdge - padding.left - padding.right;
+                int height = constrainedShortEdge - padding.top - padding.bottom;
+                mLandscapeCellLayoutMetrics = new Rect();
+                mLandscapeCellLayoutMetrics.set(
+                        grid.calculateCellWidth(width, countX),
+                        grid.calculateCellHeight(height, countY), 0, 0);
+            }
+            return mLandscapeCellLayoutMetrics;
+        } else if (orientation == CellLayout.PORTRAIT) {
+            if (mPortraitCellLayoutMetrics == null) {
+                Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
+                int width = constrainedShortEdge - padding.left - padding.right;
+                int height = constrainedLongEdge - padding.top - padding.bottom;
+                mPortraitCellLayoutMetrics = new Rect();
+                mPortraitCellLayoutMetrics.set(
+                        grid.calculateCellWidth(width, countX),
+                        grid.calculateCellHeight(height, countY), 0, 0);
+            }
+            return mPortraitCellLayoutMetrics;
+        }
+        return null;
+    }
+
+    public void onDragExit(DragObject d) {
+        mDragEnforcer.onDragExit();
+
+        // Here we store the final page that will be dropped to, if the workspace in fact
+        // receives the drop
+        if (mInScrollArea) {
+            if (isPageMoving()) {
+                // If the user drops while the page is scrolling, we should use that page as the
+                // destination instead of the page that is being hovered over.
+                mDropToLayout = (CellLayout) getPageAt(getNextPage());
+            } else {
+                mDropToLayout = mDragOverlappingLayout;
+            }
+        } else {
+            mDropToLayout = mDragTargetLayout;
+        }
+
+        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
+            mCreateUserFolderOnDrop = true;
+        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
+            mAddToExistingFolderOnDrop = true;
+        }
+
+        // Reset the scroll area and previous drag target
+        onResetScrollArea();
+        setCurrentDropLayout(null);
+        setCurrentDragOverlappingLayout(null);
+
+        mSpringLoadedDragController.cancel();
+
+        if (!mIsPageMoving) {
+            hideOutlines();
+        }
+    }
+
+    void setCurrentDropLayout(CellLayout layout) {
+        if (mDragTargetLayout != null) {
+            mDragTargetLayout.revertTempState();
+            mDragTargetLayout.onDragExit();
+        }
+        mDragTargetLayout = layout;
+        if (mDragTargetLayout != null) {
+            mDragTargetLayout.onDragEnter();
+        }
+        cleanupReorder(true);
+        cleanupFolderCreation();
+        setCurrentDropOverCell(-1, -1);
+    }
+
+    void setCurrentDragOverlappingLayout(CellLayout layout) {
+        if (mDragOverlappingLayout != null) {
+            mDragOverlappingLayout.setIsDragOverlapping(false);
+        }
+        mDragOverlappingLayout = layout;
+        if (mDragOverlappingLayout != null) {
+            mDragOverlappingLayout.setIsDragOverlapping(true);
+        }
+        invalidate();
+    }
+
+    void setCurrentDropOverCell(int x, int y) {
+        if (x != mDragOverX || y != mDragOverY) {
+            mDragOverX = x;
+            mDragOverY = y;
+            setDragMode(DRAG_MODE_NONE);
+        }
+    }
+
+    void setDragMode(int dragMode) {
+        if (dragMode != mDragMode) {
+            if (dragMode == DRAG_MODE_NONE) {
+                cleanupAddToFolder();
+                // We don't want to cancel the re-order alarm every time the target cell changes
+                // as this feels to slow / unresponsive.
+                cleanupReorder(false);
+                cleanupFolderCreation();
+            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
+                cleanupReorder(true);
+                cleanupFolderCreation();
+            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
+                cleanupAddToFolder();
+                cleanupReorder(true);
+            } else if (dragMode == DRAG_MODE_REORDER) {
+                cleanupAddToFolder();
+                cleanupFolderCreation();
+            }
+            mDragMode = dragMode;
+        }
+    }
+
+    private void cleanupFolderCreation() {
+        if (mDragFolderRingAnimator != null) {
+            mDragFolderRingAnimator.animateToNaturalState();
+        }
+        mFolderCreationAlarm.cancelAlarm();
+    }
+
+    private void cleanupAddToFolder() {
+        if (mDragOverFolderIcon != null) {
+            mDragOverFolderIcon.onDragExit(null);
+            mDragOverFolderIcon = null;
+        }
+    }
+
+    private void cleanupReorder(boolean cancelAlarm) {
+        // Any pending reorders are canceled
+        if (cancelAlarm) {
+            mReorderAlarm.cancelAlarm();
+        }
+        mLastReorderX = -1;
+        mLastReorderY = -1;
+    }
+
+   /*
+    *
+    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+    * coordinate space. The argument xy is modified with the return result.
+    *
+    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
+    * computing it itself; we use this to avoid redundant matrix inversions in
+    * findMatchingPageForDragOver
+    *
+    */
+   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
+       xy[0] = xy[0] - v.getLeft();
+       xy[1] = xy[1] - v.getTop();
+   }
+
+   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
+       if (r == null) {
+           r = new Rect();
+       }
+       mTempPt[0] = x;
+       mTempPt[1] = y;
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
+
+       LauncherAppState app = LauncherAppState.getInstance();
+       DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+       r = grid.getHotseatRect();
+       if (r.contains(mTempPt[0], mTempPt[1])) {
+           return true;
+       }
+       return false;
+   }
+
+   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
+       mTempPt[0] = (int) xy[0];
+       mTempPt[1] = (int) xy[1];
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
+       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
+
+       xy[0] = mTempPt[0];
+       xy[1] = mTempPt[1];
+   }
+
+   /*
+    *
+    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
+    * the parent View's coordinate space. The argument xy is modified with the return result.
+    *
+    */
+   void mapPointFromChildToSelf(View v, float[] xy) {
+       xy[0] += v.getLeft();
+       xy[1] += v.getTop();
+   }
+
+   static private float squaredDistance(float[] point1, float[] point2) {
+        float distanceX = point1[0] - point2[0];
+        float distanceY = point2[1] - point2[1];
+        return distanceX * distanceX + distanceY * distanceY;
+   }
+
+    /*
+     *
+     * This method returns the CellLayout that is currently being dragged to. In order to drag
+     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
+     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
+     *
+     * Return null if no CellLayout is currently being dragged over
+     *
+     */
+    private CellLayout findMatchingPageForDragOver(
+            DragView dragView, float originX, float originY, boolean exact) {
+        // We loop through all the screens (ie CellLayouts) and see which ones overlap
+        // with the item being dragged and then choose the one that's closest to the touch point
+        final int screenCount = getChildCount();
+        CellLayout bestMatchingScreen = null;
+        float smallestDistSoFar = Float.MAX_VALUE;
+
+        for (int i = 0; i < screenCount; i++) {
+            // The custom content screen is not a valid drag over option
+            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
+                continue;
+            }
+
+            CellLayout cl = (CellLayout) getChildAt(i);
+
+            final float[] touchXy = {originX, originY};
+            // Transform the touch coordinates to the CellLayout's local coordinates
+            // If the touch point is within the bounds of the cell layout, we can return immediately
+            cl.getMatrix().invert(mTempInverseMatrix);
+            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
+
+            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+                return cl;
+            }
+
+            if (!exact) {
+                // Get the center of the cell layout in screen coordinates
+                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
+                cellLayoutCenter[0] = cl.getWidth()/2;
+                cellLayoutCenter[1] = cl.getHeight()/2;
+                mapPointFromChildToSelf(cl, cellLayoutCenter);
+
+                touchXy[0] = originX;
+                touchXy[1] = originY;
+
+                // Calculate the distance between the center of the CellLayout
+                // and the touch point
+                float dist = squaredDistance(touchXy, cellLayoutCenter);
+
+                if (dist < smallestDistSoFar) {
+                    smallestDistSoFar = dist;
+                    bestMatchingScreen = cl;
+                }
+            }
+        }
+        return bestMatchingScreen;
+    }
+
+    // This is used to compute the visual center of the dragView. This point is then
+    // used to visualize drop locations and determine where to drop an item. The idea is that
+    // the visual center represents the user's interpretation of where the item is, and hence
+    // is the appropriate point to use when determining drop location.
+    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+            DragView dragView, float[] recycle) {
+        float res[];
+        if (recycle == null) {
+            res = new float[2];
+        } else {
+            res = recycle;
+        }
+
+        // First off, the drag view has been shifted in a way that is not represented in the
+        // x and y values or the x/yOffsets. Here we account for that shift.
+        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
+        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+
+        // These represent the visual top and left of drag view if a dragRect was provided.
+        // If a dragRect was not provided, then they correspond to the actual view left and
+        // top, as the dragRect is in that case taken to be the entire dragView.
+        // R.dimen.dragViewOffsetY.
+        int left = x - xOffset;
+        int top = y - yOffset;
+
+        // In order to find the visual center, we shift by half the dragRect
+        res[0] = left + dragView.getDragRegion().width() / 2;
+        res[1] = top + dragView.getDragRegion().height() / 2;
+
+        return res;
+    }
+
+    private boolean isDragWidget(DragObject d) {
+        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
+                d.dragInfo instanceof PendingAddWidgetInfo);
+    }
+    private boolean isExternalDragWidget(DragObject d) {
+        return d.dragSource != this && isDragWidget(d);
+    }
+
+    public void onDragOver(DragObject d) {
+        // Skip drag over events while we are dragging over side pages
+        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
+
+        Rect r = new Rect();
+        CellLayout layout = null;
+        ItemInfo item = (ItemInfo) d.dragInfo;
+
+        // Ensure that we have proper spans for the item that we are dropping
+        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
+        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+            d.dragView, mDragViewVisualCenter);
+
+        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
+        // Identify whether we have dragged over a side page
+        if (isSmall()) {
+            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
+                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+                    layout = mLauncher.getHotseat().getLayout();
+                }
+            }
+            if (layout == null) {
+                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
+            }
+            if (layout != mDragTargetLayout) {
+                setCurrentDropLayout(layout);
+                setCurrentDragOverlappingLayout(layout);
+
+                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
+                if (isInSpringLoadedMode) {
+                    if (mLauncher.isHotseatLayout(layout)) {
+                        mSpringLoadedDragController.cancel();
+                    } else {
+                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
+                    }
+                }
+            }
+        } else {
+            // Test to see if we are over the hotseat otherwise just use the current page
+            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
+                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+                    layout = mLauncher.getHotseat().getLayout();
+                }
+            }
+            if (layout == null) {
+                layout = getCurrentDropLayout();
+            }
+            if (layout != mDragTargetLayout) {
+                setCurrentDropLayout(layout);
+                setCurrentDragOverlappingLayout(layout);
+            }
+        }
+
+        // Handle the drag over
+        if (mDragTargetLayout != null) {
+            // We want the point to be mapped to the dragTarget.
+            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+            } else {
+                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+            }
+
+            ItemInfo info = (ItemInfo) d.dragInfo;
+
+            int minSpanX = item.spanX;
+            int minSpanY = item.spanY;
+            if (item.minSpanX > 0 && item.minSpanY > 0) {
+                minSpanX = item.minSpanX;
+                minSpanY = item.minSpanY;
+            }
+
+            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
+                    mDragTargetLayout, mTargetCell);
+            int reorderX = mTargetCell[0];
+            int reorderY = mTargetCell[1];
+
+            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
+
+            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
+                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
+
+            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
+                    mTargetCell[1]);
+
+            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
+                    targetCellDistance, dragOverView);
+
+            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
+                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
+                    item.spanY, child, mTargetCell);
+
+            if (!nearestDropOccupied) {
+                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
+                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
+            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
+                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
+                    mLastReorderY != reorderY)) {
+
+                // Otherwise, if we aren't adding to or creating a folder and there's no pending
+                // reorder, then we schedule a reorder
+                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
+                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
+                mReorderAlarm.setOnAlarmListener(listener);
+                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
+            }
+
+            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
+                    !nearestDropOccupied) {
+                if (mDragTargetLayout != null) {
+                    mDragTargetLayout.revertTempState();
+                }
+            }
+        }
+    }
+
+    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
+            int[] targetCell, float distance, View dragOverView) {
+        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
+                false);
+
+        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
+                !mFolderCreationAlarm.alarmPending()) {
+            mFolderCreationAlarm.setOnAlarmListener(new
+                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
+            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+            return;
+        }
+
+        boolean willAddToFolder =
+                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
+
+        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
+            mDragOverFolderIcon = ((FolderIcon) dragOverView);
+            mDragOverFolderIcon.onDragEnter(info);
+            if (targetLayout != null) {
+                targetLayout.clearDragOutlines();
+            }
+            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
+            return;
+        }
+
+        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
+            setDragMode(DRAG_MODE_NONE);
+        }
+        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
+            setDragMode(DRAG_MODE_NONE);
+        }
+
+        return;
+    }
+
+    class FolderCreationAlarmListener implements OnAlarmListener {
+        CellLayout layout;
+        int cellX;
+        int cellY;
+
+        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
+            this.layout = layout;
+            this.cellX = cellX;
+            this.cellY = cellY;
+        }
+
+        public void onAlarm(Alarm alarm) {
+            if (mDragFolderRingAnimator == null) {
+                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
+            }
+            mDragFolderRingAnimator.setCell(cellX, cellY);
+            mDragFolderRingAnimator.setCellLayout(layout);
+            mDragFolderRingAnimator.animateToAcceptState();
+            layout.showFolderAccept(mDragFolderRingAnimator);
+            layout.clearDragOutlines();
+            setDragMode(DRAG_MODE_CREATE_FOLDER);
+        }
+    }
+
+    class ReorderAlarmListener implements OnAlarmListener {
+        float[] dragViewCenter;
+        int minSpanX, minSpanY, spanX, spanY;
+        DragView dragView;
+        View child;
+
+        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
+                int spanY, DragView dragView, View child) {
+            this.dragViewCenter = dragViewCenter;
+            this.minSpanX = minSpanX;
+            this.minSpanY = minSpanY;
+            this.spanX = spanX;
+            this.spanY = spanY;
+            this.child = child;
+            this.dragView = dragView;
+        }
+
+        public void onAlarm(Alarm alarm) {
+            int[] resultSpan = new int[2];
+            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
+                    mTargetCell);
+            mLastReorderX = mTargetCell[0];
+            mLastReorderY = mTargetCell[1];
+
+            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
+
+            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
+                mDragTargetLayout.revertTempState();
+            } else {
+                setDragMode(DRAG_MODE_REORDER);
+            }
+
+            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
+            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
+                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
+        }
+    }
+
+    @Override
+    public void getHitRectRelativeToDragLayer(Rect outRect) {
+        // We want the workspace to have the whole area of the display (it will find the correct
+        // cell layout to drop to in the existing drag/drop logic.
+        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
+    }
+
+    /**
+     * Add the item specified by dragInfo to the given layout.
+     * @return true if successful
+     */
+    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
+        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
+            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
+            return true;
+        }
+        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
+        return false;
+    }
+
+    private void onDropExternal(int[] touchXY, Object dragInfo,
+            CellLayout cellLayout, boolean insertAtFirst) {
+        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
+    }
+
+    /**
+     * Drop an item that didn't originate on one of the workspace screens.
+     * It may have come from Launcher (e.g. from all apps or customize), or it may have
+     * come from another app altogether.
+     *
+     * NOTE: This can also be called when we are outside of a drag event, when we want
+     * to add an item to one of the workspace screens.
+     */
+    private void onDropExternal(final int[] touchXY, final Object dragInfo,
+            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+        final Runnable exitSpringLoadedRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
+            }
+        };
+
+        ItemInfo info = (ItemInfo) dragInfo;
+        int spanX = info.spanX;
+        int spanY = info.spanY;
+        if (mDragInfo != null) {
+            spanX = mDragInfo.spanX;
+            spanY = mDragInfo.spanY;
+        }
+
+        final long container = mLauncher.isHotseatLayout(cellLayout) ?
+                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        final long screenId = getIdForScreen(cellLayout);
+        if (!mLauncher.isHotseatLayout(cellLayout)
+                && screenId != getScreenIdForPageIndex(mCurrentPage)
+                && mState != State.SPRING_LOADED) {
+            snapToScreenId(screenId, null);
+        }
+
+        if (info instanceof PendingAddItemInfo) {
+            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
+
+            boolean findNearestVacantCell = true;
+            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+                        cellLayout, mTargetCell);
+                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+                        mDragViewVisualCenter[1], mTargetCell);
+                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
+                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+                                cellLayout, mTargetCell, distance)) {
+                    findNearestVacantCell = false;
+                }
+            }
+
+            final ItemInfo item = (ItemInfo) d.dragInfo;
+            boolean updateWidgetSize = false;
+            if (findNearestVacantCell) {
+                int minSpanX = item.spanX;
+                int minSpanY = item.spanY;
+                if (item.minSpanX > 0 && item.minSpanY > 0) {
+                    minSpanX = item.minSpanX;
+                    minSpanY = item.minSpanY;
+                }
+                int[] resultSpan = new int[2];
+                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
+                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
+
+                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
+                    updateWidgetSize = true;
+                }
+                item.spanX = resultSpan[0];
+                item.spanY = resultSpan[1];
+            }
+
+            Runnable onAnimationCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    // When dragging and dropping from customization tray, we deal with creating
+                    // widgets/shortcuts/folders in a slightly different way
+                    switch (pendingInfo.itemType) {
+                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                        int span[] = new int[2];
+                        span[0] = item.spanX;
+                        span[1] = item.spanY;
+                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
+                                container, screenId, mTargetCell, span, null);
+                        break;
+                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
+                                container, screenId, mTargetCell, null);
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown item type: " +
+                                pendingInfo.itemType);
+                    }
+                }
+            };
+            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
+
+            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
+                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
+                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
+                        item.spanY);
+            }
+
+            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
+                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
+            }
+            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
+                    animationStyle, finalView, true);
+        } else {
+            // This is for other drag/drop cases, like dragging from All Apps
+            View view = null;
+
+            switch (info.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                if (info.container == NO_ID && info instanceof AppInfo) {
+                    // Came from all apps -- make a copy
+                    info = new ShortcutInfo((AppInfo) info);
+                }
+                view = mLauncher.createShortcut(R.layout.application, cellLayout,
+                        (ShortcutInfo) info);
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+                        (FolderInfo) info, mIconCache);
+                break;
+            default:
+                throw new IllegalStateException("Unknown item type: " + info.itemType);
+            }
+
+            // First we find the cell nearest to point at which the item is
+            // dropped, without any consideration to whether there is an item there.
+            if (touchXY != null) {
+                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+                        cellLayout, mTargetCell);
+                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+                        mDragViewVisualCenter[1], mTargetCell);
+                d.postAnimationRunnable = exitSpringLoadedRunnable;
+                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
+                        true, d.dragView, d.postAnimationRunnable)) {
+                    return;
+                }
+                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
+                        true)) {
+                    return;
+                }
+            }
+
+            if (touchXY != null) {
+                // when dragging and dropping, just find the closest free spot
+                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
+                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
+            } else {
+                cellLayout.findCellForSpan(mTargetCell, 1, 1);
+            }
+            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
+                    info.spanY, insertAtFirst);
+            cellLayout.onDropChild(view);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+            cellLayout.getShortcutsAndWidgets().measureChild(view);
+
+            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
+                    lp.cellX, lp.cellY);
+
+            if (d.dragView != null) {
+                // We wrap the animation call in the temporary set and reset of the current
+                // cellLayout to its final transform -- this means we animate the drag view to
+                // the correct final location.
+                setFinalTransitionTransform(cellLayout);
+                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
+                        exitSpringLoadedRunnable);
+                resetTransitionTransform(cellLayout);
+            }
+        }
+    }
+
+    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
+                widgetInfo.spanY, widgetInfo, false);
+        int visibility = layout.getVisibility();
+        layout.setVisibility(VISIBLE);
+
+        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
+        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
+        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
+                Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(b);
+
+        layout.measure(width, height);
+        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
+        layout.draw(c);
+        c.setBitmap(null);
+        layout.setVisibility(visibility);
+        return b;
+    }
+
+    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
+            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
+            boolean external, boolean scale) {
+        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
+        // location and size on the home screen.
+        int spanX = info.spanX;
+        int spanY = info.spanY;
+
+        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
+        loc[0] = r.left;
+        loc[1] = r.top;
+
+        setFinalTransitionTransform(layout);
+        float cellLayoutScale =
+                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
+        resetTransitionTransform(layout);
+
+        float dragViewScaleX;
+        float dragViewScaleY;
+        if (scale) {
+            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+        } else {
+            dragViewScaleX = 1f;
+            dragViewScaleY = 1f;
+        }
+
+        // The animation will scale the dragView about its center, so we need to center about
+        // the final location.
+        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+
+        scaleXY[0] = dragViewScaleX * cellLayoutScale;
+        scaleXY[1] = dragViewScaleY * cellLayoutScale;
+    }
+
+    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
+            final Runnable onCompleteRunnable, int animationType, final View finalView,
+            boolean external) {
+        Rect from = new Rect();
+        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
+
+        int[] finalPos = new int[2];
+        float scaleXY[] = new float[2];
+        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
+        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
+                external, scalePreview);
+
+        Resources res = mLauncher.getResources();
+        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+
+        // In the case where we've prebound the widget, we remove it from the DragLayer
+        if (finalView instanceof AppWidgetHostView && external) {
+            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
+            mLauncher.getDragLayer().removeView(finalView);
+        }
+        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
+            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
+            dragView.setCrossFadeBitmap(crossFadeBitmap);
+            dragView.crossFade((int) (duration * 0.8f));
+        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
+            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
+        }
+
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
+                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+        } else {
+            int endStyle;
+            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
+                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
+            } else {
+                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
+            }
+
+            Runnable onComplete = new Runnable() {
+                @Override
+                public void run() {
+                    if (finalView != null) {
+                        finalView.setVisibility(VISIBLE);
+                    }
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+                }
+            };
+            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
+                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+                    duration, this);
+        }
+    }
+
+    public void setFinalTransitionTransform(CellLayout layout) {
+        if (isSwitchingState()) {
+            mCurrentScale = getScaleX();
+            setScaleX(mNewScale);
+            setScaleY(mNewScale);
+        }
+    }
+    public void resetTransitionTransform(CellLayout layout) {
+        if (isSwitchingState()) {
+            setScaleX(mCurrentScale);
+            setScaleY(mCurrentScale);
+        }
+    }
+
+    /**
+     * Return the current {@link CellLayout}, correctly picking the destination
+     * screen while a scroll is in progress.
+     */
+    public CellLayout getCurrentDropLayout() {
+        return (CellLayout) getChildAt(getNextPage());
+    }
+
+    /**
+     * Return the current CellInfo describing our current drag; this method exists
+     * so that Launcher can sync this object with the correct info when the activity is created/
+     * destroyed
+     *
+     */
+    public CellLayout.CellInfo getDragInfo() {
+        return mDragInfo;
+    }
+
+    public int getRestorePage() {
+        return getNextPage() - numCustomPages();
+    }
+
+    /**
+     * Calculate the nearest cell where the given object would be dropped.
+     *
+     * pixelX and pixelY should be in the coordinate system of layout
+     */
+    private int[] findNearestArea(int pixelX, int pixelY,
+            int spanX, int spanY, CellLayout layout, int[] recycle) {
+        return layout.findNearestArea(
+                pixelX, pixelY, spanX, spanY, recycle);
+    }
+
+    void setup(DragController dragController) {
+        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
+        mDragController = dragController;
+
+        // hardware layers on children are enabled on startup, but should be disabled until
+        // needed
+        updateChildrenLayersEnabled(false);
+        setWallpaperDimension();
+    }
+
+    /**
+     * Called at the end of a drag which originated on the workspace.
+     */
+    public void onDropCompleted(final View target, final DragObject d,
+            final boolean isFlingToDelete, final boolean success) {
+        if (mDeferDropAfterUninstall) {
+            mDeferredAction = new Runnable() {
+                    public void run() {
+                        onDropCompleted(target, d, isFlingToDelete, success);
+                        mDeferredAction = null;
+                    }
+                };
+            return;
+        }
+
+        boolean beingCalledAfterUninstall = mDeferredAction != null;
+
+        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
+            if (target != this && mDragInfo != null) {
+                CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
+                if (parentCell != null) {
+                    parentCell.removeView(mDragInfo.cell);
+                }
+                if (mDragInfo.cell instanceof DropTarget) {
+                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
+                }
+                // If we move the item to anything not on the Workspace, check if any empty
+                // screens need to be removed. If we dropped back on the workspace, this will
+                // be done post drop animation.
+                stripEmptyScreens();
+            }
+        } else if (mDragInfo != null) {
+            CellLayout cellLayout;
+            if (mLauncher.isHotseatLayout(target)) {
+                cellLayout = mLauncher.getHotseat().getLayout();
+            } else {
+                cellLayout = getScreenWithId(mDragInfo.screenId);
+            }
+            cellLayout.onDropChild(mDragInfo.cell);
+        }
+        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
+                && mDragInfo.cell != null) {
+            mDragInfo.cell.setVisibility(VISIBLE);
+        }
+        mDragOutline = null;
+        mDragInfo = null;
+    }
+
+    public void deferCompleteDropAfterUninstallActivity() {
+        mDeferDropAfterUninstall = true;
+    }
+
+    /// maybe move this into a smaller part
+    public void onUninstallActivityReturned(boolean success) {
+        mDeferDropAfterUninstall = false;
+        mUninstallSuccessful = success;
+        if (mDeferredAction != null) {
+            mDeferredAction.run();
+        }
+    }
+
+    void updateItemLocationsInDatabase(CellLayout cl) {
+        int count = cl.getShortcutsAndWidgets().getChildCount();
+
+        long screenId = getIdForScreen(cl);
+        int container = Favorites.CONTAINER_DESKTOP;
+
+        if (mLauncher.isHotseatLayout(cl)) {
+            screenId = -1;
+            container = Favorites.CONTAINER_HOTSEAT;
+        }
+
+        for (int i = 0; i < count; i++) {
+            View v = cl.getShortcutsAndWidgets().getChildAt(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            // Null check required as the AllApps button doesn't have an item info
+            if (info != null && info.requiresDbUpdate) {
+                info.requiresDbUpdate = false;
+                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
+                        info.cellY, info.spanX, info.spanY);
+            }
+        }
+    }
+
+    ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
+        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
+        getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            CellLayout cl = (CellLayout) getChildAt(i);
+            getUniqueIntents(cl, uniqueIntents, duplicates, false);
+        }
+        return uniqueIntents;
+    }
+
+    void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
+            ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
+        int count = cl.getShortcutsAndWidgets().getChildCount();
+
+        ArrayList<View> children = new ArrayList<View>();
+        for (int i = 0; i < count; i++) {
+            View v = cl.getShortcutsAndWidgets().getChildAt(i);
+            children.add(v);
+        }
+
+        for (int i = 0; i < count; i++) {
+            View v = children.get(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            // Null check required as the AllApps button doesn't have an item info
+            if (info instanceof ShortcutInfo) {
+                ShortcutInfo si = (ShortcutInfo) info;
+                ComponentName cn = si.intent.getComponent();
+
+                Uri dataUri = si.intent.getData();
+                // If dataUri is not null / empty or if this component isn't one that would
+                // have previously showed up in the AllApps list, then this is a widget-type
+                // shortcut, so ignore it.
+                if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
+                    continue;
+                }
+
+                if (!uniqueIntents.contains(cn)) {
+                    uniqueIntents.add(cn);
+                } else {
+                    if (stripDuplicates) {
+                        cl.removeViewInLayout(v);
+                        LauncherModel.deleteItemFromDatabase(mLauncher, si);
+                    }
+                    if (duplicates != null) {
+                        duplicates.add(cn);
+                    }
+                }
+            }
+            if (v instanceof FolderIcon) {
+                FolderIcon fi = (FolderIcon) v;
+                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
+                for (int j = 0; j < items.size(); j++) {
+                    if (items.get(j).getTag() instanceof ShortcutInfo) {
+                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
+                        ComponentName cn = si.intent.getComponent();
+
+                        Uri dataUri = si.intent.getData();
+                        // If dataUri is not null / empty or if this component isn't one that would
+                        // have previously showed up in the AllApps list, then this is a widget-type
+                        // shortcut, so ignore it.
+                        if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
+                            continue;
+                        }
+
+                        if (!uniqueIntents.contains(cn)) {
+                            uniqueIntents.add(cn);
+                        }  else {
+                            if (stripDuplicates) {
+                                fi.getFolderInfo().remove(si);
+                                LauncherModel.deleteItemFromDatabase(mLauncher, si);
+                            }
+                            if (duplicates != null) {
+                                duplicates.add(cn);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    void saveWorkspaceToDb() {
+        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            CellLayout cl = (CellLayout) getChildAt(i);
+            saveWorkspaceScreenToDb(cl);
+        }
+    }
+
+    void saveWorkspaceScreenToDb(CellLayout cl) {
+        int count = cl.getShortcutsAndWidgets().getChildCount();
+
+        long screenId = getIdForScreen(cl);
+        int container = Favorites.CONTAINER_DESKTOP;
+
+        Hotseat hotseat = mLauncher.getHotseat();
+        if (mLauncher.isHotseatLayout(cl)) {
+            screenId = -1;
+            container = Favorites.CONTAINER_HOTSEAT;
+        }
+
+        for (int i = 0; i < count; i++) {
+            View v = cl.getShortcutsAndWidgets().getChildAt(i);
+            ItemInfo info = (ItemInfo) v.getTag();
+            // Null check required as the AllApps button doesn't have an item info
+            if (info != null) {
+                int cellX = info.cellX;
+                int cellY = info.cellY;
+                if (container == Favorites.CONTAINER_HOTSEAT) {
+                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
+                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
+                }
+                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
+                        cellY, false);
+            }
+            if (v instanceof FolderIcon) {
+                FolderIcon fi = (FolderIcon) v;
+                fi.getFolder().addItemLocationsInDatabase();
+            }
+        }
+    }
+
+    @Override
+    public boolean supportsFlingToDelete() {
+        return true;
+    }
+
+    @Override
+    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+        // Do nothing
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // Do nothing
+    }
+
+    public boolean isDropEnabled() {
+        return true;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        super.onRestoreInstanceState(state);
+        Launcher.setScreen(mCurrentPage);
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        // We don't dispatch restoreInstanceState to our children using this code path.
+        // Some pages will be restored immediately as their items are bound immediately, and
+        // others we will need to wait until after their items are bound.
+        mSavedStates = container;
+    }
+
+    public void restoreInstanceStateForChild(int child) {
+        if (mSavedStates != null) {
+            mRestoredPages.add(child);
+            CellLayout cl = (CellLayout) getChildAt(child);
+            cl.restoreInstanceState(mSavedStates);
+        }
+    }
+
+    public void restoreInstanceStateForRemainingPages() {
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            if (!mRestoredPages.contains(i)) {
+                restoreInstanceStateForChild(i);
+            }
+        }
+        mRestoredPages.clear();
+    }
+
+    @Override
+    public void scrollLeft() {
+        if (!isSmall() && !mIsSwitchingState) {
+            super.scrollLeft();
+        }
+        Folder openFolder = getOpenFolder();
+        if (openFolder != null) {
+            openFolder.completeDragExit();
+        }
+    }
+
+    @Override
+    public void scrollRight() {
+        if (!isSmall() && !mIsSwitchingState) {
+            super.scrollRight();
+        }
+        Folder openFolder = getOpenFolder();
+        if (openFolder != null) {
+            openFolder.completeDragExit();
+        }
+    }
+
+    @Override
+    public boolean onEnterScrollArea(int x, int y, int direction) {
+        // Ignore the scroll area if we are dragging over the hot seat
+        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
+        if (mLauncher.getHotseat() != null && isPortrait) {
+            Rect r = new Rect();
+            mLauncher.getHotseat().getHitRect(r);
+            if (r.contains(x, y)) {
+                return false;
+            }
+        }
+
+        boolean result = false;
+        if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
+            mInScrollArea = true;
+
+            final int page = getNextPage() +
+                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
+            // We always want to exit the current layout to ensure parity of enter / exit
+            setCurrentDropLayout(null);
+
+            if (0 <= page && page < getChildCount()) {
+                // Ensure that we are not dragging over to the custom content screen
+                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
+                    return false;
+                }
+
+                CellLayout layout = (CellLayout) getChildAt(page);
+                setCurrentDragOverlappingLayout(layout);
+
+                // Workspace is responsible for drawing the edge glow on adjacent pages,
+                // so we need to redraw the workspace when this may have changed.
+                invalidate();
+                result = true;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean onExitScrollArea() {
+        boolean result = false;
+        if (mInScrollArea) {
+            invalidate();
+            CellLayout layout = getCurrentDropLayout();
+            setCurrentDropLayout(layout);
+            setCurrentDragOverlappingLayout(layout);
+
+            result = true;
+            mInScrollArea = false;
+        }
+        return result;
+    }
+
+    private void onResetScrollArea() {
+        setCurrentDragOverlappingLayout(null);
+        mInScrollArea = false;
+    }
+
+    /**
+     * Returns a specific CellLayout
+     */
+    CellLayout getParentCellLayoutForView(View v) {
+        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
+        for (CellLayout layout : layouts) {
+            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
+                return layout;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a list of all the CellLayouts in the workspace.
+     */
+    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
+        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
+        int screenCount = getChildCount();
+        for (int screen = 0; screen < screenCount; screen++) {
+            layouts.add(((CellLayout) getChildAt(screen)));
+        }
+        if (mLauncher.getHotseat() != null) {
+            layouts.add(mLauncher.getHotseat().getLayout());
+        }
+        return layouts;
+    }
+
+    /**
+     * We should only use this to search for specific children.  Do not use this method to modify
+     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
+     * the hotseat and workspace pages
+     */
+    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
+        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+                new ArrayList<ShortcutAndWidgetContainer>();
+        int screenCount = getChildCount();
+        for (int screen = 0; screen < screenCount; screen++) {
+            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
+        }
+        if (mLauncher.getHotseat() != null) {
+            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
+        }
+        return childrenLayouts;
+    }
+
+    public Folder getFolderForTag(Object tag) {
+        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+                getAllShortcutAndWidgetContainers();
+        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+            int count = layout.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View child = layout.getChildAt(i);
+                if (child instanceof Folder) {
+                    Folder f = (Folder) child;
+                    if (f.getInfo() == tag && f.getInfo().opened) {
+                        return f;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public View getViewForTag(Object tag) {
+        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+                getAllShortcutAndWidgetContainers();
+        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+            int count = layout.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View child = layout.getChildAt(i);
+                if (child.getTag() == tag) {
+                    return child;
+                }
+            }
+        }
+        return null;
+    }
+
+    void clearDropTargets() {
+        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+                getAllShortcutAndWidgetContainers();
+        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+            int childCount = layout.getChildCount();
+            for (int j = 0; j < childCount; j++) {
+                View v = layout.getChildAt(j);
+                if (v instanceof DropTarget) {
+                    mDragController.removeDropTarget((DropTarget) v);
+                }
+            }
+        }
+    }
+
+    // Removes ALL items that match a given package name, this is usually called when a package
+    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
+    // belong to that package.
+    void removeItemsByPackageName(final ArrayList<String> packages) {
+        final HashSet<String> packageNames = new HashSet<String>();
+        packageNames.addAll(packages);
+
+        // Filter out all the ItemInfos that this is going to affect
+        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
+        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
+        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+        for (CellLayout layoutParent : cellLayouts) {
+            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+            int childCount = layout.getChildCount();
+            for (int i = 0; i < childCount; ++i) {
+                View view = layout.getChildAt(i);
+                infos.add((ItemInfo) view.getTag());
+            }
+        }
+        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
+            @Override
+            public boolean filterItem(ItemInfo parent, ItemInfo info,
+                                      ComponentName cn) {
+                if (packageNames.contains(cn.getPackageName())) {
+                    cns.add(cn);
+                    return true;
+                }
+                return false;
+            }
+        };
+        LauncherModel.filterItemInfos(infos, filter);
+
+        // Remove the affected components
+        removeItemsByComponentName(cns);
+    }
+
+    // Removes items that match the application info specified, when applications are removed
+    // as a part of an update, this is called to ensure that other widgets and application
+    // shortcuts are not removed.
+    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
+        // Just create a hash table of all the specific components that this will affect
+        HashSet<ComponentName> cns = new HashSet<ComponentName>();
+        for (AppInfo info : appInfos) {
+            cns.add(info.componentName);
+        }
+
+        // Remove all the things
+        removeItemsByComponentName(cns);
+    }
+
+    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
+        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+        for (final CellLayout layoutParent: cellLayouts) {
+            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+
+            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
+            for (int j = 0; j < layout.getChildCount(); j++) {
+                final View view = layout.getChildAt(j);
+                children.put((ItemInfo) view.getTag(), view);
+            }
+
+            final ArrayList<View> childrenToRemove = new ArrayList<View>();
+            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
+                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
+            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
+                @Override
+                public boolean filterItem(ItemInfo parent, ItemInfo info,
+                                          ComponentName cn) {
+                    if (parent instanceof FolderInfo) {
+                        if (componentNames.contains(cn)) {
+                            FolderInfo folder = (FolderInfo) parent;
+                            ArrayList<ShortcutInfo> appsToRemove;
+                            if (folderAppsToRemove.containsKey(folder)) {
+                                appsToRemove = folderAppsToRemove.get(folder);
+                            } else {
+                                appsToRemove = new ArrayList<ShortcutInfo>();
+                                folderAppsToRemove.put(folder, appsToRemove);
+                            }
+                            appsToRemove.add((ShortcutInfo) info);
+                            return true;
+                        }
+                    } else {
+                        if (componentNames.contains(cn)) {
+                            childrenToRemove.add(children.get(info));
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            };
+            LauncherModel.filterItemInfos(children.keySet(), filter);
+
+            // Remove all the apps from their folders
+            for (FolderInfo folder : folderAppsToRemove.keySet()) {
+                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
+                for (ShortcutInfo info : appsToRemove) {
+                    folder.remove(info);
+                }
+            }
+
+            // Remove all the other children
+            for (View child : childrenToRemove) {
+                // Note: We can not remove the view directly from CellLayoutChildren as this
+                // does not re-mark the spaces as unoccupied.
+                layoutParent.removeViewInLayout(child);
+                if (child instanceof DropTarget) {
+                    mDragController.removeDropTarget((DropTarget) child);
+                }
+            }
+
+            if (childrenToRemove.size() > 0) {
+                layout.requestLayout();
+                layout.invalidate();
+            }
+        }
+
+        // Strip all the empty screens
+        stripEmptyScreens();
+    }
+
+    void updateShortcuts(ArrayList<AppInfo> apps) {
+        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
+        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+            int childCount = layout.getChildCount();
+            for (int j = 0; j < childCount; j++) {
+                final View view = layout.getChildAt(j);
+                Object tag = view.getTag();
+
+                if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
+                    ShortcutInfo info = (ShortcutInfo) tag;
+
+                    final Intent intent = info.intent;
+                    final ComponentName name = intent.getComponent();
+                    final int appCount = apps.size();
+                    for (int k = 0; k < appCount; k++) {
+                        AppInfo app = apps.get(k);
+                        if (app.componentName.equals(name)) {
+                            BubbleTextView shortcut = (BubbleTextView) view;
+                            info.updateIcon(mIconCache);
+                            info.title = app.title.toString();
+                            shortcut.applyFromShortcutInfo(info, mIconCache);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void moveToScreen(int page, boolean animate) {
+        if (!isSmall()) {
+            if (animate) {
+                snapToPage(page);
+            } else {
+                setCurrentPage(page);
+            }
+        }
+        View child = getChildAt(page);
+        if (child != null) {
+            child.requestFocus();
+        }
+    }
+
+    void moveToDefaultScreen(boolean animate) {
+        moveToScreen(mDefaultPage, animate);
+    }
+
+    void moveToCustomContentScreen(boolean animate) {
+        if (hasCustomContent()) {
+            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+            if (animate) {
+                snapToPage(ccIndex);
+            } else {
+                setCurrentPage(ccIndex);
+            }
+            View child = getChildAt(ccIndex);
+            if (child != null) {
+                child.requestFocus();
+            }
+         }
+    }
+
+    @Override
+    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+        long screenId = getScreenIdForPageIndex(pageIndex);
+        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+            int count = mScreenOrder.size() - numCustomPages();
+            if (count > 1) {
+                return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
+                        R.drawable.ic_pageindicator_add);
+            }
+        }
+
+        return super.getPageIndicatorMarker(pageIndex);
+    }
+
+    @Override
+    public void syncPages() {
+    }
+
+    @Override
+    public void syncPageItems(int page, boolean immediate) {
+    }
+
+    protected String getPageIndicatorDescription() {
+        String settings = getResources().getString(R.string.settings_button_text);
+        return getCurrentPageDescription() + ", " + settings;
+    }
+
+    protected String getCurrentPageDescription() {
+        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        int delta = numCustomPages();
+        if (hasCustomContent() && getNextPage() == 0) {
+            return mCustomContentDescription;
+        }
+        return String.format(getContext().getString(R.string.workspace_scroll_format),
+                page + 1 - delta, getChildCount() - delta);
+    }
+
+    public void getLocationInDragLayer(int[] loc) {
+        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+    }
+}
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
new file mode 100644
index 0000000..db25076
--- /dev/null
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -0,0 +1,22 @@
+/*
+ * 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.launcher3.config;
+
+public class ProviderConfig {
+
+    public static final String AUTHORITY = "com.android.launcher3.settings";
+}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 0000000..5f64018
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,260 @@
+/*
+ * 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.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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 boolean REUSE_BITMAP =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+    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 = 1024;
+
+    BitmapRegionDecoder 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 Rect mOverlapRegion = new Rect();
+    private BitmapFactory.Options mOptions;
+    private Canvas mCanvas;
+
+    public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
+        this(null, context, path, null, 0, previewSize, rotation);
+    }
+
+    public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
+        this(null, context, null, uri, 0, previewSize, rotation);
+    }
+
+    public BitmapRegionTileSource(Resources res,
+            Context context, int resId, int previewSize, int rotation) {
+        this(res, context, null, null, resId, previewSize, rotation);
+    }
+
+    private BitmapRegionTileSource(Resources res,
+            Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
+        mTileSize = TiledImageRenderer.suggestedTileSize(context);
+        mRotation = rotation;
+        try {
+            if (path != null) {
+                mDecoder = BitmapRegionDecoder.newInstance(path, true);
+            } else if (uri != null) {
+                InputStream is = context.getContentResolver().openInputStream(uri);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+            } else {
+                InputStream is = res.openRawResource(resId);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+            }
+            mWidth = mDecoder.getWidth();
+            mHeight = mDecoder.getHeight();
+        } catch (IOException e) {
+            Log.w("BitmapRegionTileSource", "ctor failed", e);
+        }
+        mOptions = new BitmapFactory.Options();
+        mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        mOptions.inPreferQualityOverSpeed = true;
+        mOptions.inTempStorage = new byte[16 * 1024];
+        if (previewSize != 0) {
+            previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+            // Although this is the same size as the Bitmap that is likely already
+            // loaded, the lifecycle is different and interactions are on a different
+            // thread. Thus to simplify, this source will decode its own bitmap.
+            Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
+            if (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.getWidth(), preview.getHeight()));
+            }
+        }
+    }
+
+    @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();
+        if (!REUSE_BITMAP) {
+            return getTileWithoutReusingBitmap(level, x, y, tileSize);
+        }
+
+        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;
+    }
+
+    private Bitmap getTileWithoutReusingBitmap(
+            int level, int x, int y, int tileSize) {
+
+        int t = tileSize << level;
+        mWantRegion.set(x, y, x + t, y + t);
+
+        mOverlapRegion.set(0, 0, mWidth, mHeight);
+
+        mOptions.inSampleSize = (1 << level);
+        Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
+
+        if (bitmap == null) {
+            Log.w(TAG, "fail in decoding region");
+        }
+
+        if (mWantRegion.equals(mOverlapRegion)) {
+            return bitmap;
+        }
+
+        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
+        if (mCanvas == null) {
+            mCanvas = new Canvas();
+        }
+        mCanvas.setBitmap(result);
+        mCanvas.drawBitmap(bitmap,
+                (mOverlapRegion.left - mWantRegion.left) >> level,
+                (mOverlapRegion.top - mWantRegion.top) >> level, null);
+        mCanvas.setBitmap(null);
+        return result;
+    }
+
+    /**
+     * Note that the returned bitmap may have a long edge that's longer
+     * than the targetSize, but it will always be less than 2x the targetSize
+     */
+    private Bitmap decodePreview(
+            Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
+        float scale = (float) targetSize / Math.max(mWidth, mHeight);
+        mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+        mOptions.inJustDecodeBounds = false;
+
+        Bitmap result = null;
+        if (file != null) {
+            result = BitmapFactory.decodeFile(file, mOptions);
+        } else if (uri != null) {
+            try {
+                InputStream is = context.getContentResolver().openInputStream(uri);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                result = BitmapFactory.decodeStream(bis, null, mOptions);
+            } catch (IOException e) {
+                Log.w("BitmapRegionTileSource", "getting preview failed", e);
+            }
+        } else {
+            result = BitmapFactory.decodeResource(res, resId, mOptions);
+        }
+        if (result == null) {
+            return null;
+        }
+
+        // We need to resize down if the decoder does not support inSampleSize
+        // or didn't support the specified inSampleSize (some decoders only do powers of 2)
+        scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
+
+        if (scale <= 0.5) {
+            result = BitmapUtils.resizeBitmapByScale(result, scale, true);
+        }
+        return ensureGLCompatibleBitmap(result);
+    }
+
+    private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
+        if (bitmap == null || bitmap.getConfig() != null) {
+            return bitmap;
+        }
+        Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
+        bitmap.recycle();
+        return newBitmap;
+    }
+}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
new file mode 100644
index 0000000..8a05051
--- /dev/null
+++ b/src/com/android/photos/views/BlockingGLTextureView.java
@@ -0,0 +1,438 @@
+/*
+ * 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.SurfaceTexture;
+import android.opengl.GLSurfaceView.Renderer;
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A TextureView that supports blocking rendering for synchronous drawing
+ */
+public class BlockingGLTextureView extends TextureView
+        implements SurfaceTextureListener {
+
+    private RenderThread mRenderThread;
+
+    public BlockingGLTextureView(Context context) {
+        super(context);
+        setSurfaceTextureListener(this);
+    }
+
+    public void setRenderer(Renderer renderer) {
+        if (mRenderThread != null) {
+            throw new IllegalArgumentException("Renderer already set");
+        }
+        mRenderThread = new RenderThread(renderer);
+    }
+
+    public void render() {
+        mRenderThread.render();
+    }
+
+    public void destroy() {
+        if (mRenderThread != null) {
+            mRenderThread.finish();
+            mRenderThread = null;
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
+            int height) {
+        mRenderThread.setSurface(surface);
+        mRenderThread.setSize(width, height);
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
+            int height) {
+        mRenderThread.setSize(width, height);
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        if (mRenderThread != null) {
+            mRenderThread.setSurface(null);
+        }
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } catch (Throwable t) {
+            // Ignore
+        }
+        super.finalize();
+    }
+
+    /**
+     * An EGL helper class.
+     */
+
+    private static class EglHelper {
+        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        private static final int EGL_OPENGL_ES2_BIT = 4;
+
+        EGL10 mEgl;
+        EGLDisplay mEglDisplay;
+        EGLSurface mEglSurface;
+        EGLConfig mEglConfig;
+        EGLContext mEglContext;
+
+        private EGLConfig chooseEglConfig() {
+            int[] configsCount = new int[1];
+            EGLConfig[] configs = new EGLConfig[1];
+            int[] configSpec = getConfig();
+            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+                throw new IllegalArgumentException("eglChooseConfig failed " +
+                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            } else if (configsCount[0] > 0) {
+                return configs[0];
+            }
+            return null;
+        }
+
+        private static int[] getConfig() {
+            return new int[] {
+                    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                    EGL10.EGL_RED_SIZE, 8,
+                    EGL10.EGL_GREEN_SIZE, 8,
+                    EGL10.EGL_BLUE_SIZE, 8,
+                    EGL10.EGL_ALPHA_SIZE, 8,
+                    EGL10.EGL_DEPTH_SIZE, 0,
+                    EGL10.EGL_STENCIL_SIZE, 0,
+                    EGL10.EGL_NONE
+            };
+        }
+
+        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+            int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
+        }
+
+        /**
+         * Initialize EGL for a given configuration spec.
+         */
+        public void start() {
+            /*
+             * Get an EGL instance
+             */
+            mEgl = (EGL10) EGLContext.getEGL();
+
+            /*
+             * Get to the default display.
+             */
+            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+
+            /*
+             * We can now initialize EGL for that display
+             */
+            int[] version = new int[2];
+            if (!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
+            mEglConfig = chooseEglConfig();
+
+            /*
+            * Create an EGL context. We want to do this as rarely as we can, because an
+            * EGL context is a somewhat heavy object.
+            */
+            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+                mEglContext = null;
+                throwEglException("createContext");
+            }
+
+            mEglSurface = null;
+        }
+
+        /**
+         * Create an egl surface for the current SurfaceTexture surface. If a surface
+         * already exists, destroy it before creating the new surface.
+         *
+         * @return true if the surface was created successfully.
+         */
+        public boolean createSurface(SurfaceTexture surface) {
+            /*
+             * Check preconditions.
+             */
+            if (mEgl == null) {
+                throw new RuntimeException("egl not initialized");
+            }
+            if (mEglDisplay == null) {
+                throw new RuntimeException("eglDisplay not initialized");
+            }
+            if (mEglConfig == null) {
+                throw new RuntimeException("mEglConfig not initialized");
+            }
+
+            /*
+             *  The window size has changed, so we need to create a new
+             *  surface.
+             */
+            destroySurfaceImp();
+
+            /*
+             * Create an EGL surface we can render into.
+             */
+            if (surface != null) {
+                mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
+            } else {
+                mEglSurface = null;
+            }
+
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                int error = mEgl.eglGetError();
+                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                }
+                return false;
+            }
+
+            /*
+             * Before we can issue GL commands, we need to make sure
+             * the context is current and bound to a surface.
+             */
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                /*
+                 * Could not make the context current, probably because the underlying
+                 * SurfaceView surface has been destroyed.
+                 */
+                logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Create a GL object for the current EGL context.
+         */
+        public GL10 createGL() {
+            return (GL10) mEglContext.getGL();
+        }
+
+        /**
+         * Display the current render surface.
+         * @return the EGL error code from eglSwapBuffers.
+         */
+        public int swap() {
+            if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                return mEgl.eglGetError();
+            }
+            return EGL10.EGL_SUCCESS;
+        }
+
+        public void destroySurface() {
+            destroySurfaceImp();
+        }
+
+        private void destroySurfaceImp() {
+            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_CONTEXT);
+                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+                mEglSurface = null;
+            }
+        }
+
+        public void finish() {
+            if (mEglContext != null) {
+                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+                mEglContext = null;
+            }
+            if (mEglDisplay != null) {
+                mEgl.eglTerminate(mEglDisplay);
+                mEglDisplay = null;
+            }
+        }
+
+        private void throwEglException(String function) {
+            throwEglException(function, mEgl.eglGetError());
+        }
+
+        public static void throwEglException(String function, int error) {
+            String message = formatEglError(function, error);
+            throw new RuntimeException(message);
+        }
+
+        public static void logEglErrorAsWarning(String tag, String function, int error) {
+            Log.w(tag, formatEglError(function, error));
+        }
+
+        public static String formatEglError(String function, int error) {
+            return function + " failed: " + error;
+        }
+
+    }
+
+    private static class RenderThread extends Thread {
+        private static final int INVALID = -1;
+        private static final int RENDER = 1;
+        private static final int CHANGE_SURFACE = 2;
+        private static final int RESIZE_SURFACE = 3;
+        private static final int FINISH = 4;
+
+        private EglHelper mEglHelper = new EglHelper();
+
+        private Object mLock = new Object();
+        private int mExecMsgId = INVALID;
+        private SurfaceTexture mSurface;
+        private Renderer mRenderer;
+        private int mWidth, mHeight;
+
+        private boolean mFinished = false;
+        private GL10 mGL;
+
+        public RenderThread(Renderer renderer) {
+            super("RenderThread");
+            mRenderer = renderer;
+            start();
+        }
+
+        private void checkRenderer() {
+            if (mRenderer == null) {
+                throw new IllegalArgumentException("Renderer is null!");
+            }
+        }
+
+        private void checkSurface() {
+            if (mSurface == null) {
+                throw new IllegalArgumentException("surface is null!");
+            }
+        }
+
+        public void setSurface(SurfaceTexture surface) {
+            // If the surface is null we're being torn down, don't need a
+            // renderer then
+            if (surface != null) {
+                checkRenderer();
+            }
+            mSurface = surface;
+            exec(CHANGE_SURFACE);
+        }
+
+        public void setSize(int width, int height) {
+            checkRenderer();
+            checkSurface();
+            mWidth = width;
+            mHeight = height;
+            exec(RESIZE_SURFACE);
+        }
+
+        public void render() {
+            checkRenderer();
+            if (mSurface != null) {
+                exec(RENDER);
+                mSurface.updateTexImage();
+            }
+        }
+
+        public void finish() {
+            mSurface = null;
+            exec(FINISH);
+            try {
+                join();
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+        private void exec(int msgid) {
+            synchronized (mLock) {
+                if (mExecMsgId != INVALID) {
+                    throw new IllegalArgumentException(
+                            "Message already set - multithreaded access?");
+                }
+                mExecMsgId = msgid;
+                mLock.notify();
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        private void handleMessageLocked(int what) {
+            switch (what) {
+            case CHANGE_SURFACE:
+                if (mEglHelper.createSurface(mSurface)) {
+                    mGL = mEglHelper.createGL();
+                    mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
+                }
+                break;
+            case RESIZE_SURFACE:
+                mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
+                break;
+            case RENDER:
+                mRenderer.onDrawFrame(mGL);
+                mEglHelper.swap();
+                break;
+            case FINISH:
+                mEglHelper.destroySurface();
+                mEglHelper.finish();
+                mFinished = true;
+                break;
+            }
+        }
+
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                mEglHelper.start();
+                while (!mFinished) {
+                    while (mExecMsgId == INVALID) {
+                        try {
+                            mLock.wait();
+                        } catch (InterruptedException e) {
+                            // Ignore
+                        }
+                    }
+                    handleMessageLocked(mExecMsgId);
+                    mExecMsgId = INVALID;
+                    mLock.notify();
+                }
+                mExecMsgId = FINISH;
+            }
+        }
+    }
+}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
new file mode 100644
index 0000000..c4e493b
--- /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.LongSparseArray;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SynchronizedPool;
+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..af4199c
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -0,0 +1,386 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.RectF;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.view.View;
+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}
+ * or {@link BlockingGLTextureView}.
+ */
+public class TiledImageView extends FrameLayout {
+
+    private static final boolean USE_TEXTURE_VIEW = false;
+    private static final boolean IS_SUPPORTED =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+    private static final boolean USE_CHOREOGRAPHER =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
+    private BlockingGLTextureView mTextureView;
+    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 static boolean isTilingSupported() {
+        return IS_SUPPORTED;
+    }
+
+    public TiledImageView(Context context) {
+        this(context, null);
+    }
+
+    public TiledImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        if (!IS_SUPPORTED) {
+            return;
+        }
+
+        mRenderer = new ImageRendererWrapper();
+        mRenderer.image = new TiledImageRenderer(this);
+        View view;
+        if (USE_TEXTURE_VIEW) {
+            mTextureView = new BlockingGLTextureView(context);
+            mTextureView.setRenderer(new TileRenderer());
+            view = mTextureView;
+        } else {
+            mGLSurfaceView = new GLSurfaceView(context);
+            mGLSurfaceView.setEGLContextClientVersion(2);
+            mGLSurfaceView.setRenderer(new TileRenderer());
+            mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+            view = mGLSurfaceView;
+        }
+        addView(view, new LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        //setTileSource(new ColoredTiles());
+    }
+
+    public void destroy() {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (USE_TEXTURE_VIEW) {
+            mTextureView.destroy();
+        } else {
+            mGLSurfaceView.queueEvent(mFreeTextures);
+        }
+    }
+
+    private Runnable mFreeTextures = new Runnable() {
+
+        @Override
+        public void run() {
+            mRenderer.image.freeTextures();
+        }
+    };
+
+    public void onPause() {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (!USE_TEXTURE_VIEW) {
+            mGLSurfaceView.onPause();
+        }
+    }
+
+    public void onResume() {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (!USE_TEXTURE_VIEW) {
+            mGLSurfaceView.onResume();
+        }
+    }
+
+    public void setTileSource(TileSource source, Runnable isReadyCallback) {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        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();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right,
+            int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        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
+    protected void dispatchDraw(Canvas canvas) {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (USE_TEXTURE_VIEW) {
+            mTextureView.render();
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    @SuppressLint("NewApi")
+    @Override
+    public void setTranslationX(float translationX) {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        super.setTranslationX(translationX);
+    }
+
+    @Override
+    public void invalidate() {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (USE_TEXTURE_VIEW) {
+            super.invalidate();
+            mTextureView.invalidate();
+        } else {
+            if (USE_CHOREOGRAPHER) {
+                invalOnVsync();
+            } else {
+                mGLSurfaceView.requestRender();
+            }
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    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 RectF mTempRectF = new RectF();
+    public void positionFromMatrix(Matrix matrix) {
+        if (!IS_SUPPORTED) {
+            return;
+        }
+        if (mRenderer.source != null) {
+            final int rotation = mRenderer.source.getRotation();
+            final boolean swap = !(rotation % 180 == 0);
+            final int width = swap ? mRenderer.source.getImageHeight()
+                    : mRenderer.source.getImageWidth();
+            final int height = swap ? mRenderer.source.getImageWidth()
+                    : mRenderer.source.getImageHeight();
+            mTempRectF.set(0, 0, width, height);
+            matrix.mapRect(mTempRectF);
+            matrix.getValues(mValues);
+            int cx = width / 2;
+            int cy = height / 2;
+            float scale = mValues[Matrix.MSCALE_X];
+            int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
+            int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
+            if (rotation == 90 || rotation == 180) {
+                cx += (mTempRectF.left / scale) - xoffset;
+            } else {
+                cx -= (mTempRectF.left / scale) - xoffset;
+            }
+            if (rotation == 180 || rotation == 270) {
+                cy += (mTempRectF.top / scale) - yoffset;
+            } else {
+                cy -= (mTempRectF.top / scale) - yoffset;
+            }
+            mRenderer.scale = scale;
+            mRenderer.centerX = swap ? cy : cx;
+            mRenderer.centerY = swap ? cx : cy;
+            invalidate();
+        }
+    }
+
+    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);
+                }
+            }
+        }
+
+    }
+
+    @SuppressWarnings("unused")
+    private static class ColoredTiles implements TileSource {
+        private static final int[] COLORS = new int[] {
+            Color.RED,
+            Color.BLUE,
+            Color.YELLOW,
+            Color.GREEN,
+            Color.CYAN,
+            Color.MAGENTA,
+            Color.WHITE,
+        };
+
+        private Paint mPaint = new Paint();
+        private Canvas mCanvas = new Canvas();
+
+        @Override
+        public int getTileSize() {
+            return 256;
+        }
+
+        @Override
+        public int getImageWidth() {
+            return 16384;
+        }
+
+        @Override
+        public int getImageHeight() {
+            return 8192;
+        }
+
+        @Override
+        public int getRotation() {
+            return 0;
+        }
+
+        @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);
+            }
+            mCanvas.setBitmap(bitmap);
+            mCanvas.drawColor(COLORS[level]);
+            mPaint.setColor(Color.BLACK);
+            mPaint.setTextSize(20);
+            mPaint.setTextAlign(Align.CENTER);
+            mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
+            tileSize <<= level;
+            x /= tileSize;
+            y /= tileSize;
+            mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
+            mCanvas.setBitmap(null);
+            return bitmap;
+        }
+
+        @Override
+        public BasicTexture getPreview() {
+            return null;
+        }
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..762a52b
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,16 @@
+# 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.
+#
+#LOCAL_PATH := $(call my-dir)
+#include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/stress/Android.mk b/tests/stress/Android.mk
new file mode 100644
index 0000000..68289bd
--- /dev/null
+++ b/tests/stress/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := LauncherRotationStressTest
+
+LOCAL_CERTIFICATE := shared
+
+LOCAL_INSTRUMENTATION_FOR := Launcher2
+
+include $(BUILD_PACKAGE)
diff --git a/tests/stress/AndroidManifest.xml b/tests/stress/AndroidManifest.xml
new file mode 100644
index 0000000..bcca1ff
--- /dev/null
+++ b/tests/stress/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="2.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3.stress.launcherrotation">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.launcher3"
+        android:label="Rotation stress test using Launcher2">
+    </instrumentation>
+</manifest>
diff --git a/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java b/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java
new file mode 100644
index 0000000..a5b85eb
--- /dev/null
+++ b/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3.stress;
+
+
+import com.android.launcher3.Launcher;
+
+import android.content.pm.ActivityInfo;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.RepetitiveTest;
+import android.util.Log;
+
+/**
+ * Run rotation stress test using Launcher2 for 50 iterations.
+ */
+public class LauncherRotationStressTest extends ActivityInstrumentationTestCase2<Launcher> {
+
+    private static final int NUM_ITERATIONS = 50;
+    private static final int WAIT_TIME_MS = 500;
+    private static final String LOG_TAG = "LauncherRotationStressTest";
+
+    public LauncherRotationStressTest() {
+        super(Launcher.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @RepetitiveTest(numIterations=NUM_ITERATIONS)
+    public void testLauncherRotationStress() throws Exception {
+        Launcher launcher = getActivity();
+        getInstrumentation().waitForIdleSync();
+        SystemClock.sleep(WAIT_TIME_MS);
+        launcher.setRequestedOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        getInstrumentation().waitForIdleSync();
+        SystemClock.sleep(WAIT_TIME_MS);
+        launcher.setRequestedOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+    }
+}
diff --git a/update_gallery_files.py b/update_gallery_files.py
new file mode 100644
index 0000000..ef4e8c9
--- /dev/null
+++ b/update_gallery_files.py
@@ -0,0 +1,54 @@
+# This script is used to pull the most up-to-date files from
+# Gallery into Launcher (we use some code from the Gallery
+# source). The Launcher versions have some small modifications
+# so do this with care, and be sure you are pulling from the
+# latest version of Gallery
+import os
+import sys
+files = """
+src/android/util/Pools.java
+src/com/android/gallery3d/util/IntArray.java
+src/com/android/gallery3d/common/Utils.java
+src/com/android/gallery3d/exif/ByteBufferInputStream.java
+src/com/android/gallery3d/exif/CountedDataInputStream.java
+src/com/android/gallery3d/exif/ExifData.java
+src/com/android/gallery3d/exif/ExifInterface.java
+src/com/android/gallery3d/exif/ExifInvalidFormatException.java
+src/com/android/gallery3d/exif/ExifModifier.java
+src/com/android/gallery3d/exif/ExifOutputStream.java
+src/com/android/gallery3d/exif/ExifParser.java
+src/com/android/gallery3d/exif/ExifReader.java
+src/com/android/gallery3d/exif/ExifTag.java
+src/com/android/gallery3d/exif/IfdData.java
+src/com/android/gallery3d/exif/IfdId.java
+src/com/android/gallery3d/exif/JpegHeader.java
+src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+src/com/android/gallery3d/exif/Rational.java
+src/com/android/gallery3d/glrenderer/BasicTexture.java
+src/com/android/gallery3d/glrenderer/BitmapTexture.java
+src/com/android/gallery3d/glrenderer/GLCanvas.java
+src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
+src/com/android/gallery3d/glrenderer/GLId.java
+src/com/android/gallery3d/glrenderer/GLPaint.java
+src/com/android/gallery3d/glrenderer/RawTexture.java
+src/com/android/gallery3d/glrenderer/Texture.java
+src/com/android/gallery3d/glrenderer/UploadedTexture.java
+src/com/android/photos/BitmapRegionTileSource.java
+src/com/android/photos/views/BlockingGLTextureView.java
+src/com/android/photos/views/TiledImageRenderer.java
+src/com/android/photos/views/TiledImageView.java
+src/com/android/gallery3d/common/BitmapUtils.java
+"""
+
+if len(sys.argv) != 2:
+    print "Usage: python update_gallery_files.py <gallery_dir>"
+    exit()
+gallery_dir = sys.argv[1]
+for file_path in files.split():
+    dir = os.path.dirname(file_path)
+    if file_path.find('exif') != -1 or file_path.find('common') != -1:
+        file_path = 'gallerycommon/' + file_path
+    cmd = 'cp %s/%s %s/' % (gallery_dir, file_path, dir)
+    print cmd
+    os.system(cmd)
diff --git a/util/com/android/launcher3/DecoderRing.java b/util/com/android/launcher3/DecoderRing.java
new file mode 100644
index 0000000..1d9e0de
--- /dev/null
+++ b/util/com/android/launcher3/DecoderRing.java
@@ -0,0 +1,214 @@
+/*
+ * 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.launcher3;
+
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Resource;
+import com.android.launcher3.backup.BackupProtos.Screen;
+import com.android.launcher3.backup.BackupProtos.Widget;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.System;
+import java.util.zip.CRC32;
+
+/**
+ * Commandline utility for decoding protos written to the android logs during debugging.
+ *
+ * base64 -D icon.log > icon.bin
+ * java -classpath $ANDROID_HOST_OUT/framework/protoutil.jar:$ANDROID_HOST_OUT/../common/obj/JAVA_LIBRARIES/host-libprotobuf-java-2.3.0-nano_intermediates/javalib.jar \
+ *   com.android.launcher3.DecoderRing -i icon.bin
+ *
+ * TODO: write a wrapper to setup the classpath
+ */
+class DecoderRing {
+    public static void main(String[ ] args)
+            throws Exception {
+        File source = null;
+        Class type = Key.class;
+        int skip = 0;
+
+        for (int i = 0; i < args.length; i++) {
+            if ("-k".equals(args[i])) {
+                type = Key.class;
+            } else if ("-f".equals(args[i])) {
+                type = Favorite.class;
+            } else if ("-j".equals(args[i])) {
+                type = Journal.class;
+            } else if ("-i".equals(args[i])) {
+                type = Resource.class;
+            } else if ("-s".equals(args[i])) {
+                type = Screen.class;
+            } else if ("-w".equals(args[i])) {
+                type = Widget.class;
+            } else if ("-S".equals(args[i])) {
+                if ((i + 1) < args.length) {
+                    skip = Integer.valueOf(args[++i]);
+                } else {
+                    usage(args);
+                }
+            } else if (args[i] != null && !args[i].startsWith("-")) {
+                source = new File(args[i]);
+            } else {
+                System.err.println("Unsupported flag: " + args[i]);
+                usage(args);
+            }
+        }
+
+
+        // read in the bytes
+        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+        BufferedInputStream input = null;
+        if (source == null) {
+            input = new BufferedInputStream(System.in);
+        } else {
+            try {
+                input = new BufferedInputStream(new FileInputStream(source));
+            } catch (FileNotFoundException e) {
+                System.err.println("failed to open file: " + source + ", " + e);
+                System.exit(1);
+            }
+        }
+        byte[] buffer = new byte[1024];
+        try {
+            while (input.available() > 0) {
+                int n = input.read(buffer);
+                int offset = 0;
+                if (skip > 0) {
+                    offset = Math.min(skip, n);
+                    n -= offset;
+                    skip -= offset;
+                }
+                if (n > 0) {
+                    byteStream.write(buffer, offset, n);
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("failed to read input: " + e);
+            System.exit(1);
+        }
+        System.err.println("read this many bytes: " + byteStream.size());
+
+        MessageNano proto = null;
+        if (type == Key.class) {
+            Key key = new Key();
+            try {
+                key = Key.parseFrom(byteStream.toByteArray());
+            } catch (InvalidProtocolBufferNanoException e) {
+                System.err.println("failed to parse proto: " + e);
+                System.exit(1);
+            }
+            // keys are self-checked
+            if (key.checksum != checkKey(key)) {
+                System.err.println("key ckecksum failed");
+                System.exit(1);
+            }
+            proto = key;
+        } else {
+            // other types are wrapped in a checksum message
+            CheckedMessage wrapper = new CheckedMessage();
+            try {
+                MessageNano.mergeFrom(wrapper, byteStream.toByteArray());
+            } catch (InvalidProtocolBufferNanoException e) {
+                System.err.println("failed to parse wrapper: " + e);
+                System.exit(1);
+            }
+            CRC32 checksum = new CRC32();
+            checksum.update(wrapper.payload);
+            if (wrapper.checksum != checksum.getValue()) {
+                System.err.println("wrapper ckecksum failed");
+                System.exit(1);
+            }
+            // decode the actual message
+            proto = (MessageNano) type.newInstance();
+            try {
+                MessageNano.mergeFrom(proto, wrapper.payload);
+            } catch (InvalidProtocolBufferNanoException e) {
+                System.err.println("failed to parse proto: " + e);
+                System.exit(1);
+            }
+        }
+
+        // Generic string output
+        System.out.println(proto.toString());
+
+        // save off the icon bits in a file for inspection
+        if (proto instanceof Resource) {
+            Resource icon = (Resource) proto;
+            final String path = "icon.webp";
+            FileOutputStream iconFile = new FileOutputStream(path);
+            iconFile.write(icon.data);
+            iconFile.close();
+            System.err.println("wrote " + path);
+        }
+
+        // save off the widget icon and preview bits in files for inspection
+        if (proto instanceof Widget) {
+            Widget widget = (Widget) proto;
+            if (widget.icon != null) {
+                final String path = "widget_icon.webp";
+                FileOutputStream iconFile = new FileOutputStream(path);
+                iconFile.write(widget.icon.data);
+                iconFile.close();
+                System.err.println("wrote " + path);
+            }
+            if (widget.preview != null) {
+                final String path = "widget_preview.webp";
+                FileOutputStream iconFile = new FileOutputStream(path);
+                iconFile.write(widget.preview.data);
+                iconFile.close();
+                System.err.println("wrote " + path);
+            }
+        }
+
+        // success
+        System.exit(0);
+    }
+
+    private static long checkKey(Key key) {
+        CRC32 checksum = new CRC32();
+        checksum.update(key.type);
+        checksum.update((int) (key.id & 0xffff));
+        checksum.update((int) ((key.id >> 32) & 0xffff));
+        if (key.name != null && key.name.length() > 0) {
+            checksum.update(key.name.getBytes());
+        }
+        return checksum.getValue();
+    }
+
+    private static void usage(String[] args) {
+        System.err.println("DecoderRing type [input]");
+        System.err.println("\t-k\tdecode a key");
+        System.err.println("\t-f\tdecode a favorite");
+        System.err.println("\t-i\tdecode a icon");
+        System.err.println("\t-s\tdecode a screen");
+        System.err.println("\t-w\tdecode a widget");
+        System.err.println("\t-s b\tskip b bytes");
+        System.err.println("\tfilename\tread from filename, not stdin");
+        System.exit(1);
+    }
+}
\ No newline at end of file